diff options
Diffstat (limited to 'src')
283 files changed, 19434 insertions, 7311 deletions
diff --git a/src/2geom/path-intersection.cpp b/src/2geom/path-intersection.cpp index b2d5ceabb..2e4eba519 100644 --- a/src/2geom/path-intersection.cpp +++ b/src/2geom/path-intersection.cpp @@ -41,16 +41,16 @@ int winding(Path const &path, Point p) { starting = false; Rect bounds = *(iter->boundsFast()); Coord x = p[X], y = p[Y]; - + if(x > bounds.right() || !bounds[Y].contains(y)) continue; //ray doesn't intersect box - + Point final = iter->finalPoint(); Point initial = iter->initialPoint(); Cmp final_to_ray = cmp(final[Y], y); Cmp initial_to_ray = cmp(initial[Y], y); - + // if y is included, these will have opposite values, giving order. - Cmp c = cmp(final_to_ray, initial_to_ray); + Cmp c = cmp(final_to_ray, initial_to_ray); if(x < bounds.left()) { // ray goes through bbox // winding delta determined by position of endpoints @@ -100,7 +100,7 @@ int winding(Path const &path, Point p) { //Looks like it looped, which means everything's flat return 0; } - + cont:(void)0; } return wind; @@ -118,7 +118,7 @@ bool path_direction(Path const &p) { double x = p.initialPoint()[X]; Cmp res = cmp(p[0].finalPoint()[Y], y); goto doh; - for(unsigned i = 1; i <= p.size(); i++) { + for(unsigned i = 1; i < p.size(); i++) { Cmp final_to_ray = cmp(p[i].finalPoint()[Y], y); Cmp initial_to_ray = cmp(p[i].initialPoint()[Y], y); // if y is included, these will have opposite values, giving order. @@ -135,10 +135,10 @@ bool path_direction(Path const &p) { } else if(final_to_ray == EQUAL_TO) goto doh; } return res < 0; - + doh: //Otherwise fallback on area - + Piecewise<D2<SBasis> > pw = p.toPwSb(); double area; Point centre; @@ -214,36 +214,34 @@ intersect_polish_f (const gsl_vector * x, void *params, { const double x0 = gsl_vector_get (x, 0); const double x1 = gsl_vector_get (x, 1); - - Geom::Point dx = ((struct rparams *) params)->A(x0) - + + Geom::Point dx = ((struct rparams *) params)->A(x0) - ((struct rparams *) params)->B(x1); - + gsl_vector_set (f, 0, dx[0]); gsl_vector_set (f, 1, dx[1]); - + return GSL_SUCCESS; } #endif -static void +static void intersect_polish_root (Curve const &A, double &s, Curve const &B, double &t) { - int status; - size_t iter = 0; std::vector<Point> as, bs; as = A.pointAndDerivatives(s, 2); bs = B.pointAndDerivatives(t, 2); Point F = as[0] - bs[0]; double best = dot(F, F); - + for(int i = 0; i < 4; i++) { - + /** we want to solve J*(x1 - x0) = f(x0) - + |dA(s)[0] -dB(t)[0]| (X1 - X0) = A(s) - B(t) - |dA(s)[1] -dB(t)[1]| + |dA(s)[1] -dB(t)[1]| **/ // We're using the standard transformation matricies, which is numerically rather poor. Much better to solve the equation using elimination. @@ -259,7 +257,7 @@ intersect_polish_root (Curve const &A, double &s, else if (ns>1) ns=1; if (nt<0) nt=0; else if (nt>1) nt=1; - + as = A.pointAndDerivatives(ns, 2); bs = B.pointAndDerivatives(nt, 2); F = as[0] - bs[0]; @@ -277,33 +275,35 @@ intersect_polish_root (Curve const &A, double &s, const size_t n = 2; struct rparams p = {A, B}; gsl_multiroot_function f = {&intersect_polish_f, n, &p}; - + double x_init[2] = {s, t}; gsl_vector *x = gsl_vector_alloc (n); - + gsl_vector_set (x, 0, x_init[0]); gsl_vector_set (x, 1, x_init[1]); - + const gsl_multiroot_fsolver_type *T = gsl_multiroot_fsolver_hybrids; gsl_multiroot_fsolver *sol = gsl_multiroot_fsolver_alloc (T, 2); gsl_multiroot_fsolver_set (sol, &f, x); - + + int status = 0; + size_t iter = 0; do { iter++; status = gsl_multiroot_fsolver_iterate (sol); - + if (status) /* check if solver is stuck */ break; - + status = gsl_multiroot_test_residual (sol->f, 1e-12); } while (status == GSL_CONTINUE && iter < 1000); - + s = gsl_vector_get (sol->x, 0); t = gsl_vector_get (sol->x, 1); - + gsl_multiroot_fsolver_free (sol); gsl_vector_free (x); } @@ -315,7 +315,7 @@ intersect_polish_root (Curve const &A, double &s, * It passes in the curves, time intervals, and keeps track of depth, while * returning the results through the Crossings parameter. */ -void pair_intersect(Curve const & A, double Al, double Ah, +void pair_intersect(Curve const & A, double Al, double Ah, Curve const & B, double Bl, double Bh, Crossings &ret, unsigned depth = 0) { // std::cout << depth << "(" << Al << ", " << Ah << ")\n"; @@ -324,15 +324,15 @@ void pair_intersect(Curve const & A, double Al, double Ah, OptRect Br = B.boundsLocal(Interval(Bl, Bh)); if (!Br) return; - + if(! Ar->intersects(*Br)) return; - + //Checks the general linearity of the function - if((depth > 12)) { // || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 + if((depth > 12)) { // || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 //&& B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) { double tA, tB, c; - if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), - B.pointAt(Bl), B.pointAt(Bh), + if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), + B.pointAt(Bl), B.pointAt(Bh), tA, tB, c)) { tA = tA * (Ah - Al) + Al; tB = tB * (Bh - Bl) + Bl; @@ -385,8 +385,8 @@ void mono_intersect(Curve const &A, double Al, double Ah, if(depth > 12 || (Ar.maxExtent() < tol && Ar.maxExtent() < tol)) { double tA, tB, c; - if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), - B.pointAt(Bl), B.pointAt(Bh), + if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), + B.pointAt(Bl), B.pointAt(Bh), tA, tB, c)) { tA = tA * (Ah - Al) + Al; tB = tB * (Bh - Bl) + Bl; @@ -483,9 +483,9 @@ std::vector<double> offset_doubles(std::vector<double> const &x, double offs) { std::vector<double> path_mono_splits(Path const &p) { std::vector<double> ret; if(p.empty()) return ret; - + bool pdx=2, pdy=2; //Previous derivative direction - for(unsigned i = 0; i <= p.size(); i++) { + for(unsigned i = 0; i < p.size(); i++) { std::vector<double> spl = offset_doubles(curve_mono_splits(p[i]), i); bool dx = p[i].initialPoint()[X] > (spl.empty()? p[i].finalPoint()[X] : p.valueAt(spl.front(), X)); @@ -502,7 +502,7 @@ std::vector<double> path_mono_splits(Path const &p) { } /** - * Applies path_mono_splits to multiple paths, and returns the results such that + * Applies path_mono_splits to multiple paths, and returns the results such that * time-set i corresponds to Path i. */ std::vector<std::vector<double> > paths_mono_splits(std::vector<Path> const &ps) { @@ -541,14 +541,14 @@ CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> if(b.empty()) return CrossingSet(a.size(), Crossings()); CrossingSet results(a.size() + b.size(), Crossings()); if(a.empty()) return results; - + std::vector<std::vector<double> > splits_a = paths_mono_splits(a), splits_b = paths_mono_splits(b); std::vector<std::vector<Rect> > bounds_a = split_bounds(a, splits_a), bounds_b = split_bounds(b, splits_b); - - std::vector<Rect> bounds_a_union, bounds_b_union; + + std::vector<Rect> bounds_a_union, bounds_b_union; for(unsigned i = 0; i < bounds_a.size(); i++) bounds_a_union.push_back(union_list(bounds_a[i])); for(unsigned i = 0; i < bounds_b.size(); i++) bounds_b_union.push_back(union_list(bounds_b[i])); - + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds_a_union, bounds_b_union); Crossings n; for(unsigned i = 0; i < cull.size(); i++) { @@ -556,7 +556,7 @@ CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> unsigned j = cull[i][jx]; unsigned jc = j + a.size(); Crossings res; - + //Sweep of the monotonic portions std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bounds_a[i], bounds_b[j]); for(unsigned k = 0; k < cull2.size(); k++) { @@ -567,9 +567,9 @@ CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> res, .1); } } - + for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = jc; } - + merge_crossings(results[i], res, i); merge_crossings(results[i], res, jc); } @@ -583,22 +583,22 @@ CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> CrossingSet crossings_among(std::vector<Path> const &p) { CrossingSet results(p.size(), Crossings()); if(p.empty()) return results; - + std::vector<std::vector<double> > splits = paths_mono_splits(p); std::vector<std::vector<Rect> > prs = split_bounds(p, splits); std::vector<Rect> rs; for(unsigned i = 0; i < prs.size(); i++) rs.push_back(union_list(prs[i])); - + std::vector<std::vector<unsigned> > cull = sweep_bounds(rs); - + //we actually want to do the self-intersections, so add em in: for(unsigned i = 0; i < cull.size(); i++) cull[i].push_back(i); - + for(unsigned i = 0; i < cull.size(); i++) { for(unsigned jx = 0; jx < cull[i].size(); jx++) { unsigned j = cull[i][jx]; Crossings res; - + //Sweep of the monotonic portions std::vector<std::vector<unsigned> > cull2 = sweep_bounds(prs[i], prs[j]); for(unsigned k = 0; k < cull2.size(); k++) { @@ -609,14 +609,14 @@ CrossingSet crossings_among(std::vector<Path> const &p) { res, .1); } } - + for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = j; } - + merge_crossings(results[i], res, i); merge_crossings(results[j], res, j); } } - + return results; } */ @@ -635,7 +635,7 @@ Crossings curve_self_crossings(Curve const &a) { } /* -void mono_curve_intersect(Curve const & A, double Al, double Ah, +void mono_curve_intersect(Curve const & A, double Al, double Ah, Curve const & B, double Bl, double Bh, Crossings &ret, unsigned depth=0) { // std::cout << depth << "(" << Al << ", " << Ah << ")\n"; @@ -643,9 +643,9 @@ void mono_curve_intersect(Curve const & A, double Al, double Ah, B0 = B.pointAt(Bl), B1 = B.pointAt(Bh); //inline code that this implies? (without rect/interval construction) if(!Rect(A0, A1).intersects(Rect(B0, B1)) || A0 == A1 || B0 == B1) return; - + //Checks the general linearity of the function - if((depth > 12) || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 + if((depth > 12) || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 && B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) { double tA, tB, c; if(linear_intersect(A0, A1, B0, B1, tA, tB, c)) { @@ -705,7 +705,7 @@ Crossings path_self_crossings(Path const &p) { for(unsigned jx = 0; jx < cull[i].size(); jx++) { unsigned j = cull[i][jx]; res.clear(); - + std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bnds[i], bnds[j]); for(unsigned k = 0; k < cull2.size(); k++) { for(unsigned lx = 0; lx < cull2[k].size(); lx++) { @@ -713,7 +713,7 @@ Crossings path_self_crossings(Path const &p) { mono_curve_intersect(p[i], spl[i][k-1], spl[i][k], p[j], spl[j][l-1], spl[j][l], res); } } - + //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) { Crossings res2; for(unsigned k = 0; k < res.size(); k++) { @@ -742,7 +742,7 @@ Crossings self_crossings(Path const &p) { unsigned j = cull[i][jx]; res.clear(); pair_intersect(p[i], 0, 1, p[j], 0, 1, res); - + //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) { Crossings res2; for(unsigned k = 0; k < res.size(); k++) { @@ -767,9 +767,9 @@ void flip_crossings(Crossings &crs) { CrossingSet crossings_among(std::vector<Path> const &p) { CrossingSet results(p.size(), Crossings()); if(p.empty()) return results; - + SimpleCrosser cc; - + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p)); for(unsigned i = 0; i < cull.size(); i++) { Crossings res = self_crossings(p[i]); @@ -779,7 +779,7 @@ CrossingSet crossings_among(std::vector<Path> const &p) { merge_crossings(results[i], res, i); for(unsigned jx = 0; jx < cull[i].size(); jx++) { unsigned j = cull[i][jx]; - + Crossings res = cc.crossings(p[i], p[j]); for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = j; } merge_crossings(results[i], res, i); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1885f56e5..46c2586d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,6 +86,7 @@ composite-undo-stack-observer.cpp common-context.cpp
conditions.cpp
conn-avoid-ref.cpp
+connection-points.cpp
connector-context.cpp
console-output-undo-observer.cpp
context-fns.cpp
diff --git a/src/Makefile.am b/src/Makefile.am index ecc0f522b..92e520e5b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -226,21 +226,19 @@ inkview_LDADD = $(all_libs) libinkversion_a_SOURCES = inkscape-version.cpp inkscape-version.h -if USE_SVN_VERSION -inkscape_version_deps = $(top_srcdir)/.svn/entries +if USE_BZR_VERSION +inkscape_version_deps = $(top_srcdir)/.bzr/branch/last-revision endif -# If this is an SVN snapshot build, regenerate this file every time -# someone updates the SVN working directory. +# If this is an BZR snapshot build, regenerate this file every time +# someone updates the BZR working directory. inkscape-version.cpp: $(inkscape_version_deps) - VER_PREFIX="$(VERSION)"; \ - if test -x "$(srcdir)/.svn" -a ! -z `which svn`; then \ - VER_SVNREV=" r`LANG=en svn info $(srcdir) | sed -n -e '/^Revision:/s/Revision: \(.*\)/\1/p'`"; \ - if test ! -z "`svn status -q $(srcdir)`"; then \ - VER_CUSTOM=" custom"; \ - fi; \ + VER_PREFIX="$(VERSION)";\ + VER_BZRREV=" r`bzr revno`"; \ + if test ! -z "`bzr status -S -V $(srcdir)`"; then \ + VER_CUSTOM=" custom"; \ fi; \ - VERSION="$$VER_PREFIX$$VER_SVNREV$$VER_CUSTOM"; \ + VERSION="$$VER_PREFIX$$VER_BZRREV$$VER_CUSTOM"; \ echo "namespace Inkscape { " \ "char const *version_string = \"$$VERSION\"; " \ "}" > inkscape-version.new.cpp; \ @@ -267,7 +265,7 @@ check_PROGRAMS = cxxtests # List of all tests to be run. TESTS = $(check_PROGRAMS) ../share/extensions/test/run-all-extension-tests -# XFAIL_TESTS = $(check_PROGRAMS) ../share/extensions/test/run-all-extension-tests +XFAIL_TESTS = $(check_PROGRAMS) ../share/extensions/test/run-all-extension-tests # including the the testsuites here ensures that they get distributed cxxtests_SOURCES = cxxtests.cpp libnr/nr-compose-reference.cpp $(CXXTEST_TESTSUITES) diff --git a/src/Makefile_insert b/src/Makefile_insert index ad72f5586..574dfe084 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -20,6 +20,7 @@ ink_common_sources += \ composite-undo-stack-observer.cpp \ composite-undo-stack-observer.h \ conditions.cpp conditions.h \ + connection-points.cpp connection-points.h \ conn-avoid-ref.cpp conn-avoid-ref.h \ connection-pool.h \ connector-context.cpp connector-context.h \ @@ -219,6 +220,7 @@ 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 \ @@ -266,6 +268,7 @@ CXXTEST_TESTSUITES += \ $(srcdir)/color-profile-test.h \ $(srcdir)/dir-util-test.h \ $(srcdir)/extract-uri-test.h \ + $(srcdir)/marker-test.h \ $(srcdir)/mod360-test.h \ $(srcdir)/round-test.h \ $(srcdir)/preferences-test.h \ diff --git a/src/attributes-test.h b/src/attributes-test.h index 1d8f32843..6677294f2 100644 --- a/src/attributes-test.h +++ b/src/attributes-test.h @@ -378,6 +378,8 @@ struct {char const *attr; bool supported;} const all_attrs[] = { {"inkscape:connector-type", true}, {"inkscape:connection-start", true}, {"inkscape:connection-end", true}, + {"inkscape:connection-points", true}, + {"inkscape:connector-curvature", true}, {"inkscape:connector-avoid", true}, {"inkscape:connector-spacing", true}, {"sodipodi:cx", true}, diff --git a/src/attributes.cpp b/src/attributes.cpp index 69b11fae9..f4a2a879a 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -37,6 +37,7 @@ static SPStyleProp const props[] = { {SP_ATTR_SODIPODI_INSENSITIVE, "sodipodi:insensitive"}, {SP_ATTR_SODIPODI_NONPRINTABLE, "sodipodi:nonprintable"}, {SP_ATTR_CONNECTOR_AVOID, "inkscape:connector-avoid"}, + {SP_ATTR_CONNECTION_POINTS, "inkscape:connection-points"}, {SP_ATTR_STYLE, "style"}, {SP_ATTR_TRANSFORM_CENTER_X, "inkscape:transform-center-x"}, {SP_ATTR_TRANSFORM_CENTER_Y, "inkscape:transform-center-y"}, @@ -76,6 +77,10 @@ static SPStyleProp const props[] = { {SP_ATTR_BORDERCOLOR, "bordercolor"}, {SP_ATTR_BORDEROPACITY, "borderopacity"}, {SP_ATTR_PAGECOLOR, "pagecolor"}, + {SP_ATTR_FIT_MARGIN_TOP, "fit-margin-top"}, + {SP_ATTR_FIT_MARGIN_LEFT, "fit-margin-left"}, + {SP_ATTR_FIT_MARGIN_RIGHT, "fit-margin-right"}, + {SP_ATTR_FIT_MARGIN_BOTTOM, "fit-margin-bottom"}, {SP_ATTR_INKSCAPE_PAGEOPACITY, "inkscape:pageopacity"}, {SP_ATTR_INKSCAPE_PAGESHADOW, "inkscape:pageshadow"}, {SP_ATTR_INKSCAPE_ZOOM, "inkscape:zoom"}, @@ -97,8 +102,8 @@ static SPStyleProp const props[] = { {SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS, "inkscape:snap-midpoints"}, {SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS, "inkscape:snap-object-midpoints"}, {SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS, "inkscape:snap-bbox-edge-midpoints"}, - {SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS, "inkscape:snap-bbox-midpoints"}, - {SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS, "inkscape:snap-intersection-paths"}, + {SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS, "inkscape:snap-bbox-midpoints"}, + {SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS, "inkscape:snap-intersection-paths"}, {SP_ATTR_INKSCAPE_OBJECT_PATHS, "inkscape:object-paths"}, {SP_ATTR_INKSCAPE_OBJECT_NODES, "inkscape:object-nodes"}, {SP_ATTR_INKSCAPE_BBOX_PATHS, "inkscape:bbox-paths"}, @@ -124,6 +129,7 @@ static SPStyleProp const props[] = { {SP_ATTR_CONNECTOR_TYPE, "inkscape:connector-type"}, {SP_ATTR_CONNECTION_START, "inkscape:connection-start"}, {SP_ATTR_CONNECTION_END, "inkscape:connection-end"}, + {SP_ATTR_CONNECTOR_CURVATURE, "inkscape:connector-curvature"}, /* SPRect */ {SP_ATTR_RX, "rx"}, {SP_ATTR_RY, "ry"}, diff --git a/src/attributes.h b/src/attributes.h index 52f71b203..e3399bc58 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -36,6 +36,7 @@ enum SPAttributeEnum { SP_ATTR_SODIPODI_INSENSITIVE, SP_ATTR_SODIPODI_NONPRINTABLE, SP_ATTR_CONNECTOR_AVOID, + SP_ATTR_CONNECTION_POINTS, SP_ATTR_STYLE, SP_ATTR_TRANSFORM_CENTER_X, SP_ATTR_TRANSFORM_CENTER_Y, @@ -76,6 +77,10 @@ enum SPAttributeEnum { SP_ATTR_BORDERCOLOR, SP_ATTR_BORDEROPACITY, SP_ATTR_PAGECOLOR, + SP_ATTR_FIT_MARGIN_TOP, + SP_ATTR_FIT_MARGIN_LEFT, + SP_ATTR_FIT_MARGIN_RIGHT, + SP_ATTR_FIT_MARGIN_BOTTOM, SP_ATTR_INKSCAPE_PAGEOPACITY, SP_ATTR_INKSCAPE_PAGESHADOW, SP_ATTR_INKSCAPE_ZOOM, @@ -97,7 +102,7 @@ enum SPAttributeEnum { SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS, SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS, SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS, - SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS, + SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS, //SP_ATTR_INKSCAPE_SNAP_INTERS_GRIDGUIDE, SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS, SP_ATTR_INKSCAPE_OBJECT_PATHS, @@ -124,6 +129,7 @@ enum SPAttributeEnum { SP_ATTR_CONNECTOR_TYPE, SP_ATTR_CONNECTION_START, SP_ATTR_CONNECTION_END, + SP_ATTR_CONNECTOR_CURVATURE, /* SPRect */ SP_ATTR_RX, SP_ATTR_RY, diff --git a/src/bind/javabind.cpp b/src/bind/javabind.cpp index f7022584f..6dc8c9a9b 100644 --- a/src/bind/javabind.cpp +++ b/src/bind/javabind.cpp @@ -1010,6 +1010,7 @@ bool JavaBinderyImpl::callStatic(int type, default: { err("Unknown value type: %d", v.getType()); + delete [] jvals; return false; } } @@ -1057,7 +1058,7 @@ bool JavaBinderyImpl::callStatic(int type, return false; } } - delete jvals; + delete [] jvals; String errStr = getException(); if (errStr.size()>0) { @@ -1131,6 +1132,7 @@ bool JavaBinderyImpl::callInstance( default: { err("Unknown value type: %d", v.getType()); + delete [] jvals; return false; } } @@ -1178,7 +1180,7 @@ bool JavaBinderyImpl::callInstance( return false; } } - delete jvals; + delete [] jvals; String errStr = getException(); if (errStr.size()>0) { diff --git a/src/box3d-context.cpp b/src/box3d-context.cpp index e3476deb3..c8fbfa877 100644 --- a/src/box3d-context.cpp +++ b/src/box3d-context.cpp @@ -123,16 +123,17 @@ static void sp_box3d_context_init(Box3DContext *box3d_context) static void sp_box3d_context_finish(SPEventContext *ec) { - Box3DContext *bc = SP_BOX3D_CONTEXT(ec); - SPDesktop *desktop = ec->desktop; + Box3DContext *bc = SP_BOX3D_CONTEXT(ec); + SPDesktop *desktop = ec->desktop; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); - sp_box3d_finish(bc); + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + sp_box3d_finish(bc); bc->sel_changed_connection.disconnect(); +// sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); if (((SPEventContextClass *) parent_class)->finish) { - ((SPEventContextClass *) parent_class)->finish(ec); - } + ((SPEventContextClass *) parent_class)->finish(ec); + } } @@ -179,13 +180,14 @@ static void sp_box3d_context_selection_changed(Inkscape::Selection *selection, g if (selection->perspList().size() == 1) { // selecting a single box changes the current perspective - ec->desktop->doc()->current_persp3d = selection->perspList().front(); + ec->desktop->doc()->setCurrentPersp3D(selection->perspList().front()); } } -/* create a default perspective in document defs if none is present - (can happen after 'vacuum defs' or when a pre-0.46 file is opened) */ -static void sp_box3d_context_check_for_persp_in_defs(SPDocument *document) { +/* 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 = (SPDefs *) SP_DOCUMENT_DEFS(document); bool has_persp = false; @@ -197,7 +199,7 @@ static void sp_box3d_context_check_for_persp_in_defs(SPDocument *document) { } if (!has_persp) { - document->current_persp3d = persp3d_create_xml_element (document); + document->setCurrentPersp3D(persp3d_create_xml_element (document)); } } @@ -209,8 +211,6 @@ static void sp_box3d_context_setup(SPEventContext *ec) ((SPEventContextClass *) parent_class)->setup(ec); } - sp_box3d_context_check_for_persp_in_defs(sp_desktop_document (ec->desktop)); - ec->shape_editor = new ShapeEditor(ec->desktop); SPItem *item = sp_desktop_selection(ec->desktop)->singleItem(); @@ -267,13 +267,13 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven static bool dragging; SPDesktop *desktop = event_context->desktop; + 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); Box3DContext *bc = SP_BOX3D_CONTEXT(event_context); - g_assert (SP_ACTIVE_DOCUMENT->current_persp3d); - Persp3D *cur_persp = SP_ACTIVE_DOCUMENT->current_persp3d; + Persp3D *cur_persp = document->getCurrentPersp3D(); event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); @@ -300,8 +300,14 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven bc->drag_ptB = from_2geom(button_dt); bc->drag_ptC = from_2geom(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 */ - bc->drag_origin_proj = cur_persp->tmat.preimage (from_2geom(button_dt), 0, Proj::Z); + bc->drag_origin_proj = cur_persp->perspective_impl->tmat.preimage (from_2geom(button_dt), 0, Proj::Z); bc->drag_ptB_proj = bc->drag_origin_proj; bc->drag_ptC_proj = bc->drag_origin_proj; bc->drag_ptC_proj.normalize(); @@ -355,7 +361,7 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven bc->drag_ptB = from_2geom(motion_dt); bc->drag_ptC = from_2geom(motion_dt); - bc->drag_ptB_proj = cur_persp->tmat.preimage (from_2geom(motion_dt), 0, Proj::Z); + bc->drag_ptB_proj = cur_persp->perspective_impl->tmat.preimage (from_2geom(motion_dt), 0, Proj::Z); bc->drag_ptC_proj = bc->drag_ptB_proj; bc->drag_ptC_proj.normalize(); bc->drag_ptC_proj[Proj::Z] = 0.25; @@ -364,16 +370,16 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven // perspective line from drag_ptB to vanishing point Y. if (!bc->ctrl_dragged) { /* snapping */ - Box3D::PerspectiveLine pline (bc->drag_ptB, Proj::Z, SP_ACTIVE_DOCUMENT->current_persp3d); + Box3D::PerspectiveLine pline (bc->drag_ptB, Proj::Z, document->getCurrentPersp3D()); bc->drag_ptC = pline.closest_to (from_2geom(motion_dt)); bc->drag_ptB_proj.normalize(); - bc->drag_ptC_proj = cur_persp->tmat.preimage (bc->drag_ptC, bc->drag_ptB_proj[Proj::X], Proj::X); + bc->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (bc->drag_ptC, bc->drag_ptB_proj[Proj::X], Proj::X); } else { bc->drag_ptC = from_2geom(motion_dt); bc->drag_ptB_proj.normalize(); - bc->drag_ptC_proj = cur_persp->tmat.preimage (from_2geom(motion_dt), bc->drag_ptB_proj[Proj::X], Proj::X); + bc->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (from_2geom(motion_dt), bc->drag_ptB_proj[Proj::X], Proj::X); } Geom::Point pt2g = to_2geom(bc->drag_ptC); m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE); @@ -424,43 +430,43 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven break; case GDK_bracketright: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::X, -180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, -180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; case GDK_bracketleft: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::X, 180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, 180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; case GDK_parenright: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Y, -180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, -180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; case GDK_parenleft: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Y, 180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, 180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; case GDK_braceright: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Z, -180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, -180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; case GDK_braceleft: - persp3d_rotate_VP (inkscape_active_document()->current_persp3d, Proj::Z, 180/snaps, MOD__ALT); - sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, 180/snaps, MOD__ALT); + sp_document_done(document, SP_VERB_CONTEXT_3DBOX, _("Change perspective (angle of PLs)")); ret = true; break; @@ -468,7 +474,7 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven /* TODO: what is this??? case GDK_O: if (MOD__CTRL && MOD__SHIFT) { - Box3D::create_canvas_point(persp3d_get_VP(inkscape_active_document()->current_persp3d, Proj::W).affine(), + Box3D::create_canvas_point(persp3d_get_VP(document()->getCurrentPersp3D(), Proj::W).affine(), 6, 0xff00ff00); } ret = true; @@ -483,6 +489,16 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven } break; + case GDK_p: + case GDK_P: + if (MOD__SHIFT_ONLY) { + if (document->getCurrentPersp3D()) { + persp3d_print_debugging_info (document->getCurrentPersp3D()); + } + ret = true; + } + break; + case GDK_x: case GDK_X: if (MOD__ALT_ONLY) { @@ -598,7 +614,7 @@ static void sp_box3d_drag(Box3DContext &bc, guint /*state*/) // 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" - /**** bc._vpdrag->updateDraggers(); ****/ + /* bc._vpdrag->updateDraggers(); */ sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5); } @@ -631,7 +647,7 @@ static void sp_box3d_finish(Box3DContext *bc) if ( bc->item != NULL ) { SPDesktop * desktop = SP_EVENT_CONTEXT_DESKTOP(bc); SPDocument *doc = sp_desktop_document(desktop); - if (!doc || !doc->current_persp3d) + if (!doc || !doc->getCurrentPersp3D()) return; SPBox3D *box = SP_BOX3D(bc->item); diff --git a/src/box3d.cpp b/src/box3d.cpp index 93efa5c35..aa2dc55e3 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -127,10 +127,8 @@ box3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) // TODO: Create/link to the correct perspective SPDocument *doc = SP_OBJECT_DOCUMENT(box); - if (!doc) { - g_print ("No document for the box!!!!\n"); + if (!doc) return; - } box->persp_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(box3d_ref_changed), box)); @@ -150,13 +148,33 @@ box3d_release(SPObject *object) if (box->persp_href) { g_free(box->persp_href); } + + // We have to store this here because the Persp3DReference gets destroyed below, but we need to + // access it to call persp3d_remove_box(), which cannot be called earlier because the reference + // needs to be destroyed first. + Persp3D *persp = box3d_get_perspective(box); + if (box->persp_ref) { box->persp_ref->detach(); delete box->persp_ref; box->persp_ref = NULL; } - //persp3d_remove_box (box3d_get_perspective(box), box); + if (persp) { + persp3d_remove_box (persp, box); + /* + // TODO: This deletes a perspective when the last box referring to it is gone. Eventually, + // it would be nice to have this but currently it crashes when undoing/redoing box deletion + // Reason: When redoing a box deletion, the associated perspective is deleted twice, first + // by the following code and then again by the redo mechanism! Perhaps we should perform + // deletion of the perspective from another location "outside" the undo/redo mechanism? + if (persp->perspective_impl->boxes.empty()) { + SPDocument *doc = SP_OBJECT_DOCUMENT(box); + persp->deleteObject(); + doc->setCurrentPersp3D(persp3d_document_first_persp(doc)); + } + */ + } if (((SPObjectClass *) parent_class)->release) ((SPObjectClass *) parent_class)->release(object); @@ -226,16 +244,10 @@ box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box) if (old_ref) { sp_signal_disconnect_by_data(old_ref, box); persp3d_remove_box (SP_PERSP3D(old_ref), box); - /* Note: This sometimes leads to attempts to remove boxes twice from the list of selected/transformed - boxes in a perspectives, but this should be uncritical. */ - persp3d_remove_box_transform (SP_PERSP3D(old_ref), box); } if ( SP_IS_PERSP3D(ref) && ref != box ) // FIXME: Comparisons sane? { persp3d_add_box (SP_PERSP3D(ref), box); - /* Note: This sometimes leads to attempts to add boxes twice to the list of selected/transformed - boxes in a perspectives, but this should be uncritical. */ - persp3d_add_box_transform (SP_PERSP3D(ref), box); } } @@ -278,7 +290,7 @@ static Inkscape::XML::Node *box3d_write(SPObject *object, Inkscape::XML::Documen repr->setAttribute("inkscape:perspectiveID", uri_string); g_free(uri_string); } else { - Inkscape::XML::Node *persp_repr = SP_OBJECT_REPR(doc->current_persp3d); + Inkscape::XML::Node *persp_repr = SP_OBJECT_REPR(doc->getCurrentPersp3D()); const gchar *persp_id = persp_repr->attribute("id"); gchar *href = g_strdup_printf("#%s", persp_id); repr->setAttribute("inkscape:perspectiveID", href); @@ -331,37 +343,8 @@ box3d_set_transform(SPItem *item, Geom::Matrix const &xform) { SPBox3D *box = SP_BOX3D(item); - /* check whether we need to unlink any boxes from their perspectives */ - Persp3D *persp = box3d_get_perspective(box); - Persp3D *transf_persp; - - if (sp_desktop_document(inkscape_active_desktop()) == SP_OBJECT_DOCUMENT(item) && - !persp3d_has_all_boxes_in_selection (persp)) { - std::list<SPBox3D *> selboxes = sp_desktop_selection(inkscape_active_desktop())->box3DList(); - - /* create a new perspective as a copy of the current one and link the selected boxes to it */ - transf_persp = persp3d_create_xml_element (SP_OBJECT_DOCUMENT(persp), persp); - - for (std::list<SPBox3D *>::iterator b = selboxes.begin(); b != selboxes.end(); ++b) { - box3d_switch_perspectives(*b, persp, transf_persp); - } - } else { - transf_persp = persp; - } - - /* only transform the perspective once, even if it has several selected boxes */ - if(!persp3d_was_transformed (transf_persp)) { - /* concatenate the affine transformation with the perspective mapping; this - function also triggers repr updates of boxes and the perspective itself */ - persp3d_apply_affine_transformation(transf_persp, xform); - } - - box3d_mark_transformed(box); - - if (persp3d_all_transformed(transf_persp)) { - /* all boxes were transformed; make perspective sensitive for further transformations */ - persp3d_unset_transforms(transf_persp); - } + // We don't apply the transform to the box directly but instead to its perspective (which is + // done in sp_selection_apply_affine). Here we only adjust strokes, patterns, etc. Geom::Matrix ret(Geom::Matrix(xform).without_translation()); gdouble const sw = hypot(ret[0], ret[1]); @@ -412,9 +395,9 @@ box3d_get_corner_screen (SPBox3D const *box, guint id, bool item_coords) { } Geom::Matrix const i2d (sp_item_i2d_affine (SP_ITEM(box))); if (item_coords) { - return box3d_get_perspective(box)->tmat.image(proj_corner).affine() * i2d.inverse(); + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine() * i2d.inverse(); } else { - return box3d_get_perspective(box)->tmat.image(proj_corner).affine(); + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine(); } } @@ -435,7 +418,7 @@ box3d_get_center_screen (SPBox3D *box) { return Geom::Point (NR_HUGE, NR_HUGE); } Geom::Matrix const i2d (sp_item_i2d_affine (SP_ITEM(box))); - return box3d_get_perspective(box)->tmat.image(proj_center).affine() * i2d.inverse(); + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_center).affine() * i2d.inverse(); } /* @@ -461,13 +444,13 @@ box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &sta Proj::Pt3 D_proj (x_coord, y_coord + diff_y, z_coord, 1.0); Proj::Pt3 E_proj (x_coord - diff_x, y_coord + diff_y, z_coord, 1.0); - Persp3D *persp = box3d_get_perspective(box); - Geom::Point A = persp->tmat.image(A_proj).affine(); - Geom::Point B = persp->tmat.image(B_proj).affine(); - Geom::Point C = persp->tmat.image(C_proj).affine(); - Geom::Point D = persp->tmat.image(D_proj).affine(); - Geom::Point E = persp->tmat.image(E_proj).affine(); - Geom::Point pt = persp->tmat.image(pt_proj).affine(); + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Geom::Point A = persp_impl->tmat.image(A_proj).affine(); + Geom::Point B = persp_impl->tmat.image(B_proj).affine(); + Geom::Point C = persp_impl->tmat.image(C_proj).affine(); + Geom::Point D = persp_impl->tmat.image(D_proj).affine(); + Geom::Point E = persp_impl->tmat.image(E_proj).affine(); + Geom::Point pt = persp_impl->tmat.image(pt_proj).affine(); // TODO: Replace these lines between corners with lines from a corner to a vanishing point // (this might help to prevent rounding errors if the box is small) @@ -523,7 +506,7 @@ box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &sta remember_snap_index = snap_index; result = snap_pts[snap_index]; } - return box3d_get_perspective(box)->tmat.preimage (result, z_coord, Proj::Z); + return box3d_get_perspective(box)->perspective_impl->tmat.preimage (result, z_coord, Proj::Z); } void @@ -535,8 +518,9 @@ box3d_set_corner (SPBox3D *box, const guint id, Geom::Point const &new_pos, cons /* update corners 0 and 7 according to which handle was moved and to the axes of movement */ if (!(movement & Box3D::Z)) { - Proj::Pt3 pt_proj (box3d_get_perspective(box)->tmat.preimage (new_pos, (id < 4) ? box->orig_corner0[Proj::Z] : - box->orig_corner7[Proj::Z], Proj::Z)); + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos, (id < 4) ? box->orig_corner0[Proj::Z] : + box->orig_corner7[Proj::Z], Proj::Z)); if (constrained) { pt_proj = box3d_snap (box, id, pt_proj, box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)); } @@ -553,13 +537,14 @@ box3d_set_corner (SPBox3D *box, const guint id, Geom::Point const &new_pos, cons 1.0); } else { Persp3D *persp = box3d_get_perspective(box); - Box3D::PerspectiveLine pl(persp->tmat.image( + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Box3D::PerspectiveLine pl(persp_impl->tmat.image( box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)).affine(), Proj::Z, persp); Geom::Point new_pos_snapped(pl.closest_to(new_pos)); - Proj::Pt3 pt_proj (persp->tmat.preimage (new_pos_snapped, - box3d_get_proj_corner (box, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y], - (movement & Box3D::Y) ? Proj::X : Proj::Y)); + Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos_snapped, + box3d_get_proj_corner (box, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y], + (movement & Box3D::Y) ? Proj::X : Proj::Y)); bool corner0_move_x = !(id & Box3D::X) && (movement & Box3D::X); bool corner0_move_y = !(id & Box3D::Y) && (movement & Box3D::Y); bool corner7_move_x = (id & Box3D::X) && (movement & Box3D::X); @@ -590,9 +575,9 @@ void box3d_set_center (SPBox3D *box, Geom::Point const &new_pos, Geom::Point con double radx = (box->orig_corner7[Proj::X] - box->orig_corner0[Proj::X]) / 2; double rady = (box->orig_corner7[Proj::Y] - box->orig_corner0[Proj::Y]) / 2; - Proj::Pt3 pt_proj (persp->tmat.preimage (new_pos, coord, Proj::Z)); + Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos, coord, Proj::Z)); if (constrained) { - Proj::Pt3 old_pos_proj (persp->tmat.preimage (old_pos, coord, Proj::Z)); + Proj::Pt3 old_pos_proj (persp->perspective_impl->tmat.preimage (old_pos, coord, Proj::Z)); old_pos_proj.normalize(); pt_proj = box3d_snap (box, -1, pt_proj, old_pos_proj); } @@ -612,7 +597,7 @@ void box3d_set_center (SPBox3D *box, Geom::Point const &new_pos, Geom::Point con Box3D::PerspectiveLine pl(old_pos, Proj::Z, persp); Geom::Point new_pos_snapped(pl.closest_to(new_pos)); - Proj::Pt3 pt_proj (persp->tmat.preimage (new_pos_snapped, coord, Proj::X)); + Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos_snapped, coord, Proj::X)); /* normalizing pt_proj is essential because we want to mingle affine coordinates */ pt_proj.normalize(); @@ -636,6 +621,7 @@ void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis, { Persp3D *persp = box3d_get_perspective(box); g_return_if_fail (persp); + Persp3DImpl *persp_impl = persp->perspective_impl; //box->orig_corner0.normalize(); //box->orig_corner7.normalize(); double coord = (box->orig_corner0[axis] > box->orig_corner7[axis]) ? @@ -666,10 +652,10 @@ void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis, default: return; } - corner1 = persp->tmat.image(c1).affine(); - corner2 = persp->tmat.image(c2).affine(); - corner3 = persp->tmat.image(c3).affine(); - corner4 = persp->tmat.image(c4).affine(); + corner1 = persp_impl->tmat.image(c1).affine(); + corner2 = persp_impl->tmat.image(c2).affine(); + corner3 = persp_impl->tmat.image(c3).affine(); + corner4 = persp_impl->tmat.image(c4).affine(); } /* Auxiliary function: Checks whether the half-line from A to B crosses the line segment joining C and D */ @@ -1027,7 +1013,7 @@ box3d_recompute_z_orders (SPBox3D *box) { Geom::Point dirs[3]; for (int i = 0; i < 3; ++i) { dirs[i] = persp3d_get_PL_dir_from_pt(persp, c3, Box3D::toProj(Box3D::axes[i])); - if (persp3d_VP_is_finite(persp, Proj::axes[i])) { + if (persp3d_VP_is_finite(persp->perspective_impl, Proj::axes[i])) { num_finite++; axis_finite = Box3D::axes[i]; } else { @@ -1212,7 +1198,7 @@ box3d_pt_lies_in_PL_sector (SPBox3D const *box, Geom::Point const &pt, int id1, Geom::Point c2(box3d_get_corner_screen(box, id2, false)); int ret = 0; - if (persp3d_VP_is_finite(persp, Box3D::toProj(axis))) { + if (persp3d_VP_is_finite(persp->perspective_impl, Box3D::toProj(axis))) { Geom::Point vp(persp3d_get_VP(persp, Box3D::toProj(axis)).affine()); Geom::Point v1(c1 - vp); Geom::Point v2(c2 - vp); @@ -1239,7 +1225,7 @@ int box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) { Persp3D *persp = box3d_get_perspective(box); - if (!persp3d_VP_is_finite(persp, vpdir)) { + if (!persp3d_VP_is_finite(persp->perspective_impl, vpdir)) { return 0; } else { return box3d_pt_lies_in_PL_sector(box, persp3d_get_VP(persp, vpdir).affine(), id1, id2, axis); @@ -1302,31 +1288,6 @@ box3d_check_for_swapped_coords(SPBox3D *box) { box3d_exchange_coords(box); } -void -box3d_add_to_selection(SPBox3D *box) { - Persp3D *persp = box3d_get_perspective(box); - g_return_if_fail(persp); - persp3d_add_box_transform(persp, box); -} - -void -box3d_remove_from_selection(SPBox3D *box) { - Persp3D *persp = box3d_get_perspective(box); - if (!persp) { - /* this can happen if a box is deleted through undo and the persp_ref is already detached; - should we rebuild the boxes of each perspective in this case or is it safe to leave it alone? */ - return; - } - persp3d_remove_box_transform(persp, box); -} - -void -box3d_mark_transformed(SPBox3D *box) { - Persp3D *persp = box3d_get_perspective(box); - g_return_if_fail(persp); - persp3d_set_box_transformed(persp, box, true); -} - static void box3d_extract_boxes_rec(SPObject *obj, std::list<SPBox3D *> &boxes) { if (SP_IS_BOX3D(obj)) { @@ -1360,16 +1321,13 @@ box3d_switch_perspectives(SPBox3D *box, Persp3D *old_persp, Persp3D *new_persp, Geom::Point corner0_screen = box3d_get_corner_screen(box, 0, false); Geom::Point corner7_screen = box3d_get_corner_screen(box, 7, false); - box->orig_corner0 = new_persp->tmat.preimage(corner0_screen, z0, Proj::Z); - box->orig_corner7 = new_persp->tmat.preimage(corner7_screen, z7, Proj::Z); + box->orig_corner0 = new_persp->perspective_impl->tmat.preimage(corner0_screen, z0, Proj::Z); + box->orig_corner7 = new_persp->perspective_impl->tmat.preimage(corner7_screen, z7, Proj::Z); } persp3d_remove_box (old_persp, box); persp3d_add_box (new_persp, box); - persp3d_remove_box_transform (old_persp, box); - persp3d_add_box_transform (new_persp, box); - gchar *href = g_strdup_printf("#%s", SP_OBJECT_REPR(new_persp)->attribute("id")); SP_OBJECT_REPR(box)->setAttribute("inkscape:perspectiveID", href); g_free(href); diff --git a/src/box3d.h b/src/box3d.h index b6d962a3c..9f2e1d78e 100644 --- a/src/box3d.h +++ b/src/box3d.h @@ -70,10 +70,6 @@ int box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, i void box3d_relabel_corners(SPBox3D *box); void box3d_check_for_swapped_coords(SPBox3D *box); -void box3d_add_to_selection(SPBox3D *box); -void box3d_remove_from_selection(SPBox3D *box); -void box3d_mark_transformed(SPBox3D *box); - std::list<SPBox3D *> box3d_extract_boxes(SPObject *obj); Persp3D *box3d_get_perspective(SPBox3D const *box); diff --git a/src/color-profile.cpp b/src/color-profile.cpp index 4b1307316..310a37356 100644 --- a/src/color-profile.cpp +++ b/src/color-profile.cpp @@ -2,7 +2,7 @@ # include "config.h" #endif -//#define DEBUG_LCMS +#define DEBUG_LCMS #include <glib/gstdio.h> #include <sys/fcntl.h> @@ -23,6 +23,7 @@ #endif #include "xml/repr.h" +#include "color.h" #include "color-profile.h" #include "color-profile-fns.h" #include "attributes.h" @@ -50,7 +51,7 @@ static cmsHPROFILE colorprofile_get_proof_profile_handle(); #ifdef DEBUG_LCMS extern guint update_in_progress; -#define DEBUG_MESSAGE(key, ...) \ +#define DEBUG_MESSAGE_SCISLAC(key, ...) \ {\ Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ bool dump = prefs->getBool(Glib::ustring("/options/scislac/") + #key);\ @@ -76,6 +77,13 @@ extern guint update_in_progress; gtk_widget_show_all( dialog );\ }\ } + + +#define DEBUG_MESSAGE(key, ...)\ +{\ + g_message( __VA_ARGS__ );\ +} + #endif // DEBUG_LCMS static SPObjectClass *cprof_parent_class; @@ -91,6 +99,15 @@ cmsHPROFILE ColorProfile::getSRGBProfile() { return _sRGBProf; } +cmsHPROFILE ColorProfile::_NullProf = 0; + +cmsHPROFILE ColorProfile::getNULLProfile() { + if ( !_NullProf ) { + _NullProf = cmsCreateNULLProfile(); + } + return _NullProf; +} + #endif // ENABLE_LCMS /** @@ -151,6 +168,7 @@ void ColorProfile::init( ColorProfile *cprof ) cprof->_profileSpace = icSigRgbData; cprof->_transf = 0; cprof->_revTransf = 0; + cprof->_gamutTransf = 0; #endif // ENABLE_LCMS } @@ -204,6 +222,10 @@ void ColorProfile::_clearProfile() cmsDeleteTransform( _revTransf ); _revTransf = 0; } + if ( _gamutTransf ) { + cmsDeleteTransform( _gamutTransf ); + _gamutTransf = 0; + } if ( profHandle ) { cmsCloseProfile( profHandle ); profHandle = 0; @@ -508,6 +530,32 @@ cmsHTRANSFORM ColorProfile::getTransfFromSRGB8() return _revTransf; } +cmsHTRANSFORM ColorProfile::getTransfGamutCheck() +{ + if ( !_gamutTransf ) { + _gamutTransf = cmsCreateProofingTransform(getSRGBProfile(), TYPE_RGBA_8, getNULLProfile(), TYPE_GRAY_8, profHandle, INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, (cmsFLAGS_GAMUTCHECK|cmsFLAGS_SOFTPROOFING)); + } + return _gamutTransf; +} + +bool ColorProfile::GamutCheck(SPColor color){ + BYTE outofgamut = 0; + + guint32 val = color.toRGBA32(0); + guchar check_color[4] = { + SP_RGBA32_R_U(val), + SP_RGBA32_G_U(val), + SP_RGBA32_B_U(val), + 255}; + + int alarm_r, alarm_g, alarm_b; + cmsGetAlarmCodes(&alarm_r, &alarm_g, &alarm_b); + cmsSetAlarmCodes(255, 255, 255); + cmsDoTransform(ColorProfile::getTransfGamutCheck(), &check_color, &outofgamut, 1); + cmsSetAlarmCodes(alarm_r, alarm_g, alarm_b); + return (outofgamut == 255); +} + #include <io/sys.h> diff --git a/src/color-profile.h b/src/color-profile.h index 2e57e7ef0..40d0d7698 100644 --- a/src/color-profile.h +++ b/src/color-profile.h @@ -36,11 +36,15 @@ struct ColorProfile : public SPObject { static std::list<Glib::ustring> getProfileDirs(); #if ENABLE_LCMS static cmsHPROFILE getSRGBProfile(); + static cmsHPROFILE getNULLProfile(); icColorSpaceSignature getColorSpace() const {return _profileSpace;} icProfileClassSignature getProfileClass() const {return _profileClass;} cmsHTRANSFORM getTransfToSRGB8(); cmsHTRANSFORM getTransfFromSRGB8(); + cmsHTRANSFORM getTransfGamutCheck(); + bool GamutCheck(SPColor color); + #endif // ENABLE_LCMS gchar* href; @@ -64,11 +68,13 @@ private: void _clearProfile(); static cmsHPROFILE _sRGBProf; + static cmsHPROFILE _NullProf; icProfileClassSignature _profileClass; icColorSpaceSignature _profileSpace; cmsHTRANSFORM _transf; cmsHTRANSFORM _revTransf; + cmsHTRANSFORM _gamutTransf; #endif // ENABLE_LCMS }; diff --git a/src/color.cpp b/src/color.cpp index b16d9950f..07c15ff15 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -17,6 +17,7 @@ #include <math.h> #include "color.h" #include "svg/svg-icc-color.h" +#include "svg/svg-device-color.h" #include "svg/svg-color.h" #include "svg/css-ostringstream.h" @@ -29,7 +30,8 @@ static bool profileMatches( SVGICCColor const* first, SVGICCColor const* second #define PROFILE_EPSILON 0.00000001 SPColor::SPColor() : - icc(0) + icc(0), + device(0) { v.c[0] = 0; v.c[1] = 0; @@ -37,19 +39,22 @@ SPColor::SPColor() : } SPColor::SPColor( SPColor const& other ) : - icc(0) + icc(0), + device(0) { *this = other; } SPColor::SPColor( float r, float g, float b ) : - icc(0) + icc(0), + device(0) { set( r, g, b ); } SPColor::SPColor( guint32 value ) : - icc(0) + icc(0), + device(0) { set( value ); } @@ -57,20 +62,29 @@ SPColor::SPColor( guint32 value ) : SPColor::~SPColor() { delete icc; + delete device; icc = 0; + device = 0; } SPColor& SPColor::operator= (SPColor const& other) { - SVGICCColor* tmp = other.icc ? new SVGICCColor(*other.icc) : 0; + SVGICCColor* tmp_icc = other.icc ? new SVGICCColor(*other.icc) : 0; + SVGDeviceColor* tmp_device = other.device ? new SVGDeviceColor(*other.device) : 0; + v.c[0] = other.v.c[0]; v.c[1] = other.v.c[1]; v.c[2] = other.v.c[2]; if ( icc ) { delete icc; } - icc = tmp; + icc = tmp_icc; + + if ( device ) { + delete device; + } + device = tmp_device; return *this; } @@ -86,6 +100,7 @@ bool SPColor::operator == (SPColor const& other) const && (v.c[2] != other.v.c[2]); match &= profileMatches( icc, other.icc ); +//TODO?: match &= devicecolorMatches( device, other.device ); return match; } @@ -204,6 +219,38 @@ std::string SPColor::toString() const css << ')'; } + if ( device && device->type != DEVICE_COLOR_INVALID) { + if ( !css.str().empty() ) { + css << " "; + } + + switch(device->type){ + case DEVICE_GRAY: + css << "device-gray("; + break; + case DEVICE_RGB: + css << "device-rgb("; + break; + case DEVICE_CMYK: + css << "device-cmyk("; + break; + case DEVICE_NCHANNEL: + css << "device-nchannel("; + break; + case DEVICE_COLOR_INVALID: + //should not be reached + break; + } + + for (vector<double>::const_iterator i(device->colors.begin()), + iEnd(device->colors.end()); + i != iEnd; ++i) { + if (i!=device->colors.begin()) css << ", "; + css << *i; + } + css << ')'; + } + return css.str(); } diff --git a/src/color.h b/src/color.h index bebeaec60..7fd351360 100644 --- a/src/color.h +++ b/src/color.h @@ -34,6 +34,7 @@ #define SP_RGBA32_F_COMPOSE(r,g,b,a) SP_RGBA32_U_COMPOSE (SP_COLOR_F_TO_U (r), SP_COLOR_F_TO_U (g), SP_COLOR_F_TO_U (b), SP_COLOR_F_TO_U (a)) struct SVGICCColor; +struct SVGDeviceColor; /** * An RGB color with optional icc-color part @@ -59,6 +60,7 @@ struct SPColor { std::string toString() const; SVGICCColor* icc; + SVGDeviceColor* device; union { float c[3]; } v; diff --git a/src/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp index 43c9c0b66..b2aa0ce6b 100644 --- a/src/conn-avoid-ref.cpp +++ b/src/conn-avoid-ref.cpp @@ -12,23 +12,36 @@ #include <cstring> #include <string> +#include <iostream> #include "sp-item.h" +#include "display/curve.h" +#include "2geom/line.h" +#include "2geom/crossing.h" +#include "2geom/convex-cover.h" +#include "helper/geom-curves.h" +#include "svg/stringstream.h" #include "conn-avoid-ref.h" -#include "libavoid/polyutil.h" +#include "connection-points.h" +#include "sp-conn-end.h" +#include "sp-path.h" #include "libavoid/router.h" #include "libavoid/connector.h" +#include "libavoid/geomtypes.h" #include "xml/node.h" #include "document.h" #include "desktop.h" #include "desktop-handles.h" #include "sp-namedview.h" +#include "sp-item-group.h" #include "inkscape.h" +#include <glibmm/i18n.h> + using Avoid::Router; -static Avoid::Polygn avoid_item_poly(SPItem const *item); +static Avoid::Polygon avoid_item_poly(SPItem const *item); SPAvoidRef::SPAvoidRef(SPItem *spitem) @@ -44,13 +57,17 @@ SPAvoidRef::SPAvoidRef(SPItem *spitem) SPAvoidRef::~SPAvoidRef() { _transformed_connection.disconnect(); - if (shapeRef) { + + // If the document is being destroyed then the router instance + // and the ShapeRefs will have been destroyed with it. + const bool routerInstanceExists = (item->document->router != NULL); + + if (shapeRef && routerInstanceExists) { Router *router = shapeRef->router(); - // shapeRef is finalised by delShape, - // so no memory is lost here. - router->delShape(shapeRef); - shapeRef = NULL; + router->removeShape(shapeRef); + delete shapeRef; } + shapeRef = NULL; } @@ -66,6 +83,205 @@ void SPAvoidRef::setAvoid(char const *value) } } +void print_connection_points(std::map<int, ConnectionPoint>& cp) +{ + std::map<int, ConnectionPoint>::iterator i; + for (i=cp.begin(); i!=cp.end(); ++i) + { + const ConnectionPoint& p = i->second; + std::cout<<p.id<<" "<<p.type<<" "<<p.pos[Geom::X]<<" "<<p.pos[Geom::Y]<<std::endl; + } +} + +void SPAvoidRef::setConnectionPoints(gchar const *value) +{ + std::set<int> updates; + std::set<int> deletes; + std::set<int> seen; + + if (value) + { + /* Rebuild the connection points list. + Update the connectors for which + the endpoint has changed. + */ + + gchar ** strarray = g_strsplit(value, "|", 0); + gchar ** iter = strarray; + + while (*iter != NULL) { + ConnectionPoint cp; + Inkscape::SVGIStringStream is(*iter); + is>>cp; + cp.type = ConnPointUserDefined; + + /* Mark this connection point as seen, so we can delete + the other ones. + */ + seen.insert(cp.id); + if ( connection_points.find(cp.id) != connection_points.end() ) + { + /* An already existing connection point. + Check to see if changed, and, if it is + the case, trigger connector update for + the connector attached to this connection + point. This is done by adding the + connection point to a list of connection + points to be updated. + */ + if ( connection_points[cp.id] != cp ) + // The connection point got updated. + // Put it in the update list. + updates.insert(cp.id); + } + connection_points[cp.id] = cp; + ++iter; + } + /* Delete the connection points that didn't appear + in the new connection point list. + */ + std::map<int, ConnectionPoint>::iterator it; + + for (it=connection_points.begin(); it!=connection_points.end(); ++it) + if ( seen.find(it->first) == seen.end()) + deletes.insert(it->first); + g_strfreev(strarray); + } + else + { + /* Delete all the user-defined connection points + Actually we do this by adding them to the list + of connection points to be deleted. + */ + std::map<int, ConnectionPoint>::iterator it; + + for (it=connection_points.begin(); it!=connection_points.end(); ++it) + deletes.insert(it->first); + } + /* Act upon updates and deletes. + */ + if (deletes.empty() && updates.empty()) + // Nothing to do, just return. + return; + // Get a list of attached connectors. + GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom); + for (GSList *i = conns; i != NULL; i = i->next) + { + SPPath* path = SP_PATH(i->data); + SPConnEnd** connEnds = path->connEndPair.getConnEnds(); + for (int ix=0; ix<2; ++ix) { + if (connEnds[ix]->type == ConnPointUserDefined) { + if (updates.find(connEnds[ix]->id) != updates.end()) { + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } else { + } + } + else if (deletes.find(connEnds[ix]->id) != deletes.end()) { + sp_conn_end_detach(path, ix); + } + } + } + } + g_slist_free(conns); + // Remove all deleted connection points + if (deletes.size()) + for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it) + connection_points.erase(*it); +} + +void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action) +{ + SPDocument* doc = SP_OBJECT_DOCUMENT(item); + + sp_object_setAttribute( SP_OBJECT(item), "inkscape:connection-points", value, 0 ); + item->updateRepr(); + sp_document_ensure_up_to_date(doc); + sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, action); +} + +void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + bool first = true; + int newId = 1; + if ( connection_points.size() ) + { + for (IdConnectionPointMap::iterator it = connection_points.begin(); ; ) + { + if ( first ) + { + first = false; + ostr<<it->second; + } + else + ostr<<'|'<<it->second; + IdConnectionPointMap::iterator prev_it = it; + ++it; + if ( it == connection_points.end() || prev_it->first + 1 != it->first ) + { + newId = prev_it->first + 1; + break; + } + } + } + cp.id = newId; + if ( first ) + { + first = false; + ostr<<cp; + } + else + ostr<<'|'<<cp; + + this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Add a new connection point") ); +} + +void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id ); + if ( cp_pos != connection_points.end() ) + { + bool first = true; + for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) + { + ConnectionPoint* to_write; + if ( it != cp_pos ) + to_write = &it->second; + else + to_write = &cp; + if ( first ) + { + first = false; + ostr<<*to_write; + } + else + ostr<<'|'<<*to_write; + } + this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Move a connection point") ); + } +} + +void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id ); + if ( cp_pos != connection_points.end() ) { + bool first = true; + for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) { + if ( it != cp_pos ) { + if ( first ) { + first = false; + ostr<<it->second; + } else { + ostr<<'|'<<it->second; + } + } + } + this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Remove a connection point") ); + } +} void SPAvoidRef::handleSettingChange(void) { @@ -90,33 +306,31 @@ void SPAvoidRef::handleSettingChange(void) setting = new_setting; Router *router = item->document->router; - + _transformed_connection.disconnect(); if (new_setting) { - Avoid::Polygn poly = avoid_item_poly(item); - if (poly.pn > 0) { + Avoid::Polygon poly = avoid_item_poly(item); + if (poly.size() > 0) { _transformed_connection = item->connectTransformed( sigc::ptr_fun(&avoid_item_move)); const char *id = SP_OBJECT_REPR(item)->attribute("id"); g_assert(id != NULL); - + // Get a unique ID for the item. GQuark itemID = g_quark_from_string(id); - shapeRef = new Avoid::ShapeRef(router, itemID, poly); - Avoid::freePoly(poly); - + shapeRef = new Avoid::ShapeRef(router, poly, itemID); + router->addShape(shapeRef); } } else { g_assert(shapeRef); - - // shapeRef is finalised by delShape, - // so no memory is lost here. - router->delShape(shapeRef); + + router->removeShape(shapeRef); + delete shapeRef; shapeRef = NULL; } } @@ -129,7 +343,7 @@ GSList *SPAvoidRef::getAttachedShapes(const unsigned int type) Avoid::IntList shapes; GQuark shapeId = g_quark_from_string(item->id); item->document->router->attachedShapes(shapes, shapeId, type); - + Avoid::IntList::iterator finish = shapes.end(); for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) { const gchar *connId = g_quark_to_string(*i); @@ -153,7 +367,7 @@ GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type) Avoid::IntList conns; GQuark shapeId = g_quark_from_string(item->id); item->document->router->attachedConns(conns, shapeId, type); - + Avoid::IntList::iterator finish = conns.end(); for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) { const gchar *connId = g_quark_to_string(*i); @@ -169,57 +383,178 @@ GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type) return list; } - -static Avoid::Polygn avoid_item_poly(SPItem const *item) +Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id) { + g_assert(item); + Geom::Point pos; + const Geom::Matrix& transform = sp_item_i2doc_affine(item); + // TODO investigate why this was asking for the active desktop: SPDesktop *desktop = inkscape_active_desktop(); - g_assert(desktop != NULL); - Avoid::Polygn poly; + if ( type == ConnPointDefault ) + { + // For now, just default to the centre of the item + Geom::OptRect bbox = item->getBounds(sp_item_i2doc_affine(item)); + pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0); + } + else + { + // Get coordinates from the list of connection points + // that are attached to the item + pos = connection_points[id].pos * transform; + } + + return pos; +} - // TODO: The right way to do this is to return the convex hull of - // the object, or an approximation in the case of a rounded - // object. Specific SPItems will need to have a new - // function that returns points for the convex hull. - // For some objects it is enough to feed the snappoints to - // some convex hull code, though not NR::ConvexHull as this - // only keeps the bounding box of the convex hull currently. +bool SPAvoidRef::isValidConnPointId( const int type, const int id ) +{ + if ( type < 0 || type > 1 ) + return false; + else + { + if ( type == ConnPointDefault ) + if ( id < 0 || id > 8 ) + return false; + else + { + } + else + return connection_points.find( id ) != connection_points.end(); + } + + return true; +} - // TODO: SPItem::getBounds gives the wrong result for some objects - // that have internal representations that are updated later - // by the sp_*_update functions, e.g., text. - sp_document_ensure_up_to_date(item->document); +static std::vector<Geom::Point> approxCurveWithPoints(SPCurve *curve) +{ + // The number of segments to use for not straight curves approximation + const unsigned NUM_SEGS = 4; - Geom::OptRect rHull = item->getBounds(sp_item_i2doc_affine(item)); - if (!rHull) { - return Avoid::newPoly(0); + const Geom::PathVector& curve_pv = curve->get_pathvector(); + + // The structure to hold the output + std::vector<Geom::Point> poly_points; + + // Iterate over all curves, adding the endpoints for linear curves and + // sampling the other curves + double seg_size = 1.0 / NUM_SEGS; + double at; + at = 0; + Geom::PathVector::const_iterator pit = curve_pv.begin(); + while (pit != curve_pv.end()) + { + Geom::Path::const_iterator cit = pit->begin(); + while (cit != pit->end()) + if (dynamic_cast<Geom::CubicBezier const*>(&*cit)) + { + at += seg_size; + if (at <= 1.0 ) + poly_points.push_back(cit->pointAt(at)); + else + { + at = 0.0; + ++cit; + } + } + else + { + poly_points.push_back(cit->finalPoint()); + ++cit; + } + ++pit; + } + return poly_points; +} + +static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform) +{ + // The structure to hold the output + std::vector<Geom::Point> poly_points; + + if (SP_IS_GROUP(item)) + { + SPGroup* group = SP_GROUP(item); + // consider all first-order children + for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) { + SPItem* child_item = SP_ITEM(i->data); + std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform); + poly_points.insert(poly_points.end(), child_points.begin(), child_points.end()); + } + } + else if (SP_IS_SHAPE(item)) + { + SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(item)); + // make sure it has an associated curve + if (item_curve) + { + // apply transformations (up to common ancestor) + item_curve->transform(item_transform); + std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve); + poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end()); + item_curve->unref(); + } } + return poly_points; +} +static Avoid::Polygon avoid_item_poly(SPItem const *item) +{ + SPDesktop *desktop = inkscape_active_desktop(); + g_assert(desktop != NULL); double spacing = desktop->namedview->connector_spacing; - // Add a little buffer around the edge of each object. - Geom::Rect rExpandedHull = *rHull; - rExpandedHull.expandBy(spacing); - poly = Avoid::newPoly(4); + Geom::Matrix itd_mat = sp_item_i2doc_affine(item); + std::vector<Geom::Point> hull_points; + hull_points = approxItemWithPoints(item, itd_mat); - for (unsigned n = 0; n < 4; ++n) { - Geom::Point hullPoint = rExpandedHull.corner(n); - poly.ps[n].x = hullPoint[Geom::X]; - poly.ps[n].y = hullPoint[Geom::Y]; - } + // create convex hull from all sampled points + Geom::ConvexHull hull(hull_points); + // enlarge path by "desktop->namedview->connector_spacing" + // store expanded convex hull in Avoid::Polygn + Avoid::Polygon poly; + + Geom::Line hull_edge(hull[-1], hull[0]); + Geom::Line prev_parallel_hull_edge; + prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing); + prev_parallel_hull_edge.versor(hull_edge.versor()); + int hull_size = hull.boundary.size(); + for (int i = 0; i <= hull_size; ++i) + { + hull_edge.setBy2Points(hull[i], hull[i+1]); + Geom::Line parallel_hull_edge; + parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing); + parallel_hull_edge.versor(hull_edge.versor()); + + // determine the intersection point + + Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge); + if (int_pt) + { + Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], + (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]); + poly.ps.push_back(avoid_pt); + } + else + { + // something went wrong... + std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl; + } + prev_parallel_hull_edge = parallel_hull_edge; + } return poly; } -GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, +GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, bool initialised) { for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { if (SP_IS_ITEM(child) && !desktop->isLayer(SP_ITEM(child)) && - !SP_ITEM(child)->isLocked() && + !SP_ITEM(child)->isLocked() && !desktop->itemIsHidden(SP_ITEM(child)) && (!initialised || SP_ITEM(child)->avoidRef->shapeRef) ) @@ -242,10 +577,9 @@ void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item) g_assert(shapeRef); Router *router = moved_item->document->router; - Avoid::Polygn poly = avoid_item_poly(moved_item); - if (poly.pn > 0) { - router->moveShape(shapeRef, &poly); - Avoid::freePoly(poly); + Avoid::Polygon poly = avoid_item_poly(moved_item); + if (!poly.empty()) { + router->moveShape(shapeRef, poly); } } @@ -257,7 +591,7 @@ void init_avoided_shape_geometry(SPDesktop *desktop) SPDocument *document = sp_desktop_document(desktop); bool saved = sp_document_get_undo_sensitive(document); sp_document_set_undo_sensitive(document, false); - + bool initialised = false; GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop, initialised); diff --git a/src/conn-avoid-ref.h b/src/conn-avoid-ref.h index d34d8ca2e..5dff8dd38 100644 --- a/src/conn-avoid-ref.h +++ b/src/conn-avoid-ref.h @@ -18,6 +18,8 @@ struct SPDesktop; struct SPItem; +struct ConnectionPoint; +typedef std::map<int, ConnectionPoint> IdConnectionPointMap; namespace Avoid { class ShapeRef; } class SPAvoidRef { @@ -28,9 +30,16 @@ public: // libavoid's internal representation of the item. Avoid::ShapeRef *shapeRef; + // Used for holding connection points for item + IdConnectionPointMap connection_points; + void setAvoid(char const *value); + void setConnectionPoints(gchar const *value); + void addConnectionPoint(ConnectionPoint &cp); + void updateConnectionPoint(ConnectionPoint &cp); + void deleteConnectionPoint(ConnectionPoint &cp); void handleSettingChange(void); - + // Returns a list of SPItems of all connectors/shapes attached to // this object. Pass one of the following for 'type': // Avoid::runningTo @@ -38,6 +47,9 @@ public: // Avoid::runningToAndFrom GSList *getAttachedShapes(const unsigned int type); GSList *getAttachedConnectors(const unsigned int type); + Geom::Point getConnectionPointPos(const int type, const int id); + + bool isValidConnPointId( const int type, const int id ); private: SPItem *item; @@ -48,6 +60,7 @@ private: // A sigc connection for transformed signal. sigc::connection _transformed_connection; + void setConnectionPointsAttrUndoable(const gchar* value, const gchar* action); }; extern GSList *get_avoided_items(GSList *list, SPObject *from, @@ -56,6 +69,7 @@ extern void avoid_item_move(Geom::Matrix const *mp, SPItem *moved_item); extern void init_avoided_shape_geometry(SPDesktop *desktop); static const double defaultConnSpacing = 3.0; +static const double defaultConnCurvature = 3.0; #endif /* !SEEN_CONN_AVOID_REF */ diff --git a/src/connection-points.cpp b/src/connection-points.cpp new file mode 100644 index 000000000..9ed98d211 --- /dev/null +++ b/src/connection-points.cpp @@ -0,0 +1,37 @@ +#include "connection-points.h" + + +bool ConnectionPoint::operator!=(ConnectionPoint& cp) +{ + return (id!=cp.id || type!=cp.type || dir!=cp.dir || pos!=cp.pos); +} + +bool ConnectionPoint::operator==(ConnectionPoint& cp) +{ + return (id==cp.id && type==cp.type && dir==cp.dir && pos==cp.pos); +} + + +namespace Inkscape{ + +SVGIStringStream& +operator>>(SVGIStringStream& istr, ConnectionPoint& cp) +{ + istr>>cp.id>>cp.dir>>cp.pos[Geom::X]>>cp.pos[Geom::Y]; + + return istr; +} + +SVGOStringStream& +operator<<(SVGOStringStream& ostr, const ConnectionPoint& cp) +{ + ostr<<cp.id<<' '<<cp.dir<<' '; + ::operator<<( ostr, cp.pos[Geom::X] ); + ostr<<' '; + ::operator<<( ostr, cp.pos[Geom::Y] ); + + return ostr; +} + + +} diff --git a/src/connection-points.h b/src/connection-points.h new file mode 100644 index 000000000..c1ddeb481 --- /dev/null +++ b/src/connection-points.h @@ -0,0 +1,70 @@ +#ifndef INKSCAPE_CONNECTION_POINT_H +#define INKSCAPE_CONNECTION_POINT_H + +#include <2geom/point.h> +//#include <libavoid/vertices.h> +#include <libavoid/connector.h> + +#include "svg/stringstream.h" + + +enum ConnPointType { + ConnPointDefault = 0, + ConnPointUserDefined = 1 +}; +enum ConnPointDefaultPos{ + ConnPointPosTL, // Top Left + ConnPointPosTC, // Top Centre + ConnPointPosTR, // Top Right + ConnPointPosCL, // Centre Left + ConnPointPosCC, // Centre Centre + ConnPointPosCR, // Centre Right + ConnPointPosBL, // Bottom Left + ConnPointPosBC, // Bottom Centre + ConnPointPosBR, // Bottom Right +}; + + +struct ConnectionPoint +{ + ConnectionPoint(): + type(ConnPointDefault), // default to a default connection point + id(ConnPointPosCC), // default to the centre point + pos(), + dir(Avoid::ConnDirAll) // allow any direction + { + } + // type of the connection point + // default or user-defined + int type; + + /* id of the connection point + in the case of default + connection points it specifies + which of the 9 types the + connection point is. + */ + int id; + + /* position related to parent item + in the case of default connection + points, these positions should be + computed by the item's avoidRef + */ + Geom::Point pos; + + // directions from which connections can occur + Avoid::ConnDirFlags dir; + + bool operator!=(ConnectionPoint&); + bool operator==(ConnectionPoint&); +}; + +namespace Inkscape{ + +SVGIStringStream& operator>>(SVGIStringStream& istr, ConnectionPoint& cp); +SVGOStringStream& operator<<(SVGOStringStream& ostr, const ConnectionPoint& cp); + +} + +#endif
\ No newline at end of file diff --git a/src/connector-context.cpp b/src/connector-context.cpp index 2131bdced..0fc9de9d0 100644 --- a/src/connector-context.cpp +++ b/src/connector-context.cpp @@ -4,31 +4,25 @@ * Authors: * Michael Wybrow <mjwybrow@users.sourceforge.net> * - * Copyright (C) 2005 Michael Wybrow + * Copyright (C) 2005-2008 Michael Wybrow + * Copyright (C) 2009 Monash University * * Released under GNU GPL, read the file 'COPYING' for more information * * TODO: - * o Have shapes avoid convex hulls of objects, rather than their - * bounding box. Possibly implement the unfinished ConvexHull - * class in libnr. - * (HOWEVER, using the convex hull C of a shape S does the wrong thing if a - * connector starts outside of S but inside C, or if the best route around - * an object involves going inside C but without entering S.) - * o Draw connectors to shape edges rather than bounding box. * 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 accidently being turned into connectors + * 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 Allow user-placeable connection points. * o Deal sanely with connectors with both endpoints attached to the * same connection point, and drawing of connectors attaching - * overlaping shapes (currently tries to adjust connector to be + * 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 @@ -37,16 +31,133 @@ * one of the other contexts. * o Cope with shapes whose ids change when they have attached * connectors. - * o gobble_motion_events(GDK_BUTTON1_MASK)?; + * 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. + * o Decide whether drawing/editing mode should be an Inkscape preference + * or the connector tool should always start in drawing mode. + * o Correct the problem with switching to the select tool when pressing + * space bar (there are moments when it refuses to do so). + * + * ---------------------------------------------------------------------------- + * + * mjwybrow's observations on acracan's Summer of Code connector work: + * + * - GUI comments: + * + * - Buttons for adding and removing user-specified connection + * points should probably have "+" and "-" symbols on them so they + * are consistent with the similar buttons for the node tool. + * - Controls on the connector tool be should be reordered logically, + * possibly as follows: + * + * *Connector*: [Polyline-radio-button] [Orthgonal-radio-button] + * [Curvature-control] | *Shape*: [Avoid-button] [Dont-avoid-button] + * [Spacing-control] | *Connection pts*: [Edit-mode] [Add-pt] [Rm-pt] + * + * I think that the network layout controls be moved to the + * Align and Distribute dialog (there is already the layout button + * there, but no options are exposed). + * + * 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. + * + * The Add-pt and Rm-pt buttons should be greyed out (inactive) if + * we are not in connection point editing mode. And probably also + * if there is no shape selected, i.e. at the times they have no + * effect when clicked. + * + * Likewise for the avoid/ignore shapes buttons. These should be + * inactive when a shape is not selected in the connector context. + * + * - When creating/editing connection points: + * + * - Strange things can happen if you have connectors selected, or + * try rerouting connectors by dragging their endpoints when in + * connection point editing mode. + * + * - Possibly the selected shape's connection points should always + * be shown (i.e., have knots) when in editing mode. + * + * - It is a little strange to be able to place connection points + * competely outside shapes. Especially when you later can't draw + * connectors to them since the knots are only visible when you + * are over the shape. I think that you should only be able to + * place connection points inside or on the boundary of the shape + * itself. + * + * - The intended ability to place a new point at the current cursor + * position by pressing RETURN does not seem to work. + * + * - The Status bar tooltip should change to reflect editing mode + * and tell the user about RETURN and how to use the tool. + * + * - Connection points general: + * + * - Connection points that were inside the shape can end up outside + * after a rotation is applied to the shape in the select tool. + * It doesn't seem like the correct transform is being applied to + * these, or it is being applied at the wrong time. I'd expect + * connection points to rotate with the shape, and stay at the + * same position "on the shape" + * + * - I was able to make the connectors attached to a shape fall off + * the shape after scaling it. Not sure the exact cause, but may + * require more investigation/debugging. + * + * - The user-defined connection points should be either absolute + * (as the current ones are) or defined as a percentage of the + * shape. These would be based on a toggle setting on the + * toolbar, and they would be placed in exactly the same way by + * the user. The only difference would be that they would be + * store as percentage positions in the SVG connection-points + * property and that they would update/move automatically if the + * object was resized or scaled. + * + * - Thinking more, I think you always want to store and think about + * the positions of connection points to be pre-transform, but + * obviously the shape transform is applied to them. That way, + * they will rotate and scale automatically with the shape, when + * the shape transform is altered. The Percentage version would + * compute their position from the pre-transform dimensions and + * then have the transform applied to them, for example. + * + * - The connection points in the test_connection_points.svg file + * seem to follow the shape when it is moved, but connection + * points I add to new shapes, do not follow the shape, either + * when the shape is just moved or transformed. There is + * something wrong here. What exactly should the behaviour be + * currently? + * + * - I see that connection points are specified at absolute canvas + * positions. I really think that they should be specified in + * shape coordinated relative to the shapes. There may be + * transforms applied to layers and the canvas which would make + * specifying them quite difficult. I'd expect a position of 0, 0 + * to be on the shape in question or very close to it, for example. * */ + + #include <gdk/gdkkeysyms.h> #include <string> #include <cstring> #include "connector-context.h" #include "pixmaps/cursor-connector.xpm" +#include "pixmaps/cursor-node.xpm" +//#include "pixmaps/cursor-node-m.xpm" +//#include "pixmaps/cursor-node-d.xpm" #include "xml/node-event-vector.h" #include "xml/repr.h" #include "svg/svg.h" @@ -63,11 +174,14 @@ #include "display/canvas-bpath.h" #include "display/sodipodi-ctrl.h" #include <glibmm/i18n.h> +#include <glibmm/stringutils.h> #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" @@ -79,6 +193,7 @@ static void sp_connector_context_init(SPConnectorContext *conn_context); static void sp_connector_context_dispose(GObject *object); static void sp_connector_context_setup(SPEventContext *ec); +static void sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val); static void sp_connector_context_finish(SPEventContext *ec); static gint sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event); static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); @@ -98,11 +213,14 @@ static gint connector_handle_motion_notify(SPConnectorContext *const cc, GdkEven static gint connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent); static gint connector_handle_key_press(SPConnectorContext *const cc, guint const keyval); +static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp); static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item); static void cc_clear_active_shape(SPConnectorContext *cc); static void cc_set_active_conn(SPConnectorContext *cc, SPItem *item); static void cc_clear_active_conn(SPConnectorContext *cc); static gchar *conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& w); +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_selection_changed(Inkscape::Selection *selection, gpointer data); static void cc_connector_rerouting_finish(SPConnectorContext *const cc, @@ -115,8 +233,8 @@ static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *nam gpointer data); -static Geom::Point connector_drag_origin_w(0, 0); -static bool connector_within_tolerance = false; +/*static Geom::Point connector_drag_origin_w(0, 0); +static bool connector_within_tolerance = false;*/ static SPEventContextClass *parent_class; @@ -171,6 +289,7 @@ sp_connector_context_class_init(SPConnectorContextClass *klass) object_class->dispose = sp_connector_context_dispose; event_context_class->setup = sp_connector_context_setup; + event_context_class->set = sp_connector_context_set; event_context_class->finish = sp_connector_context_finish; event_context_class->root_handler = sp_connector_context_root_handler; event_context_class->item_handler = sp_connector_context_item_handler; @@ -188,10 +307,14 @@ sp_connector_context_init(SPConnectorContext *cc) ec->xp = 0; ec->yp = 0; + cc->mode = SP_CONNECTOR_CONTEXT_DRAWING_MODE; + cc->knot_tip = 0; + cc->red_color = 0xff00007f; cc->newconn = NULL; cc->newConnRef = NULL; + cc->curvature = 0.0; cc->sel_changed_connection = sigc::connection(); @@ -204,10 +327,13 @@ sp_connector_context_init(SPConnectorContext *cc) cc->active_handle = NULL; + cc->selected_handle = NULL; + cc->clickeditem = NULL; cc->clickedhandle = NULL; - cc->connpthandle = NULL; + new (&cc->connpthandles) ConnectionPointMap(); + for (int i = 0; i < 2; ++i) { cc->endpt_handle[i] = NULL; cc->endpt_handler_id[i] = 0; @@ -226,10 +352,14 @@ sp_connector_context_dispose(GObject *object) cc->sel_changed_connection.disconnect(); - if (cc->connpthandle) { - g_object_unref(cc->connpthandle); - cc->connpthandle = NULL; + if (!cc->connpthandles.empty()) { + for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); + it != cc->connpthandles.end(); ++it) { + g_object_unref(it->first); + } + cc->connpthandles.clear(); } + cc->connpthandles.~ConnectionPointMap(); for (int i = 0; i < 2; ++i) { if (cc->endpt_handle[1]) { g_object_unref(cc->endpt_handle[i]); @@ -282,6 +412,12 @@ sp_connector_context_setup(SPEventContext *ec) // Notice the initial selection. cc_selection_changed(cc->selection, (gpointer) cc); + cc->within_tolerance = false; + + sp_event_context_read(ec, "curvature"); + sp_event_context_read(ec, "orthogonal"); + sp_event_context_read(ec, "mode"); + cc->knot_tip = cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ? cc_knot_tips[0] : cc_knot_tips[1]; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/tools/connector/selcue", 0)) { ec->enableSelectionCue(); @@ -294,6 +430,64 @@ sp_connector_context_setup(SPEventContext *ec) static void +sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); + + /* 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" ) { + cc->curvature = val->getDoubleLimited(); // prevents NaN and +/-Inf from messing up + } + else if ( name == "orthogonal" ) { + cc->isOrthogonal = val->getBool(); + } + else if ( name == "mode") + { + sp_connector_context_switch_mode(ec, val->getBool() ? SP_CONNECTOR_CONTEXT_EDITING_MODE : SP_CONNECTOR_CONTEXT_DRAWING_MODE); + } +} + +void sp_connector_context_switch_mode(SPEventContext* ec, unsigned int newMode) +{ + SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); + + cc->mode = newMode; + if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ) + { + ec->cursor_shape = cursor_connector_xpm; + cc->knot_tip = cc_knot_tips[0]; + if (cc->selected_handle) + cc_deselect_handle( cc->selected_handle ); + cc->selected_handle = NULL; + // Show all default connection points + + } + else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE ) + { + ec->cursor_shape = cursor_node_xpm; + cc->knot_tip = cc_knot_tips[1]; +/* if (cc->active_shape) + { + cc->selection->set( SP_OBJECT( cc->active_shape ) ); + } + else + { + SPItem* item = cc->selection->singleItem(); + if ( item ) + { + cc_set_active_shape(cc, item); + cc->selection->set( SP_OBJECT( item ) ); + } + }*/ + } + sp_event_context_update_cursor(ec); + +} + + +static void sp_connector_context_finish(SPEventContext *ec) { SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec); @@ -341,9 +535,12 @@ cc_clear_active_shape(SPConnectorContext *cc) cc->active_shape_layer_repr = NULL; } - // Hide the center connection point if it exists. - if (cc->connpthandle) { - sp_knot_hide(cc->connpthandle); + // Hide the connection points if they exist. + if (cc->connpthandles.size()) { + for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); + it != cc->connpthandles.end(); ++it) { + sp_knot_hide(it->first); + } } } @@ -379,16 +576,35 @@ conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p) // TODO: this will need to change when there are more connection // points available for each shape. - SPKnot *centerpt = cc->connpthandle; - if (cc->active_handle && (cc->active_handle == centerpt)) + if (cc->active_handle && (cc->connpthandles.find(cc->active_handle) != cc->connpthandles.end())) { - p = centerpt->pos; - return g_strdup_printf("#%s", SP_OBJECT_ID(cc->active_shape)); + p = cc->active_handle->pos; + const ConnectionPoint& cp = cc->connpthandles[cc->active_handle]; + return g_strdup_printf("#%s_%c_%d", SP_OBJECT_ID(cc->active_shape), + cp.type == ConnPointDefault ? 'd' : 'u' , cp.id); } return NULL; } +static void +cc_select_handle(SPKnot* knot) +{ + knot->setShape(SP_KNOT_SHAPE_SQUARE); + knot->setSize(10); + knot->setAnchor(GTK_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(GTK_ANCHOR_CENTER); + knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); + sp_knot_update_ctrl(knot); +} static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) @@ -405,24 +621,34 @@ sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, G case GDK_BUTTON_RELEASE: if (event->button.button == 1 && !event_context->space_panning) { if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) && - (connector_within_tolerance)) + (event_context->within_tolerance)) { spcc_reset_colors(cc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); } if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) { - // Doing simething else like rerouting. + // Doing something else like rerouting. break; } - // find out clicked item, disregarding groups, honoring Alt - SPItem *item_ungrouped = sp_event_context_find_item(desktop, - p, event->button.state & GDK_MOD1_MASK, TRUE); + // 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) { - cc->selection->toggle(item_ungrouped); + cc->selection->toggle(item); } else { - cc->selection->set(item_ungrouped); + cc->selection->set(item); + if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && cc->selected_handle ) + { + cc_deselect_handle( cc->selected_handle ); + cc->selected_handle = NULL; + } + /* When selecting a new item, + do not allow showing connection points + on connectors. (yet?) + */ + if ( item != cc->active_shape && !cc_item_is_connector( item ) ) + cc_set_active_shape( cc, item ); } ret = TRUE; @@ -430,15 +656,24 @@ sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, G break; case GDK_ENTER_NOTIFY: { - if (cc_item_is_shape(item)) { - // This is a shape, so show connection point(s). - if (!(cc->active_shape) || - // Don't show handle for another handle. - (item != ((SPItem *) cc->connpthandle))) { + if (cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE || (cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && !cc->selected_handle)) + { + if (cc_item_is_shape(item)) { + + // I don't really understand what the above does, + // so I commented it. + // This is a shape, so show connection point(s). + /* if (!(cc->active_shape) + // Don't show handle for another handle. + // || (cc->connpthandles.find((SPKnot*) item) != cc->connpthandles.end()) + ) + { + cc_set_active_shape(cc, item); + }*/ cc_set_active_shape(cc, item); } + ret = TRUE; } - ret = TRUE; break; } default: @@ -462,7 +697,7 @@ sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event) break; case GDK_MOTION_NOTIFY: - ret = connector_handle_motion_notify(cc, event->motion); + ret = connector_handle_motion_notify(cc, event->motion); break; case GDK_BUTTON_RELEASE: @@ -497,96 +732,160 @@ connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const SPEventContext *event_context = SP_EVENT_CONTEXT(cc); gint ret = FALSE; - if ( bevent.button == 1 && !event_context->space_panning ) { + if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ) + { + if ( bevent.button == 1 && !event_context->space_panning ) { - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) { - return TRUE; - } + if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) { + return TRUE; + } - Geom::Point const event_w(bevent.x, - bevent.y); - connector_drag_origin_w = event_w; - connector_within_tolerance = true; + Geom::Point const event_w(bevent.x, + bevent.y); +// connector_drag_origin_w = event_w; + cc->xp = bevent.x; + cc->yp = bevent.y; + cc->within_tolerance = true; - Geom::Point const event_dt = cc->desktop->w2d(event_w); + Geom::Point const event_dt = cc->desktop->w2d(event_w); - SnapManager &m = cc->desktop->namedview->snap_manager; - m.setup(cc->desktop); + SnapManager &m = cc->desktop->namedview->snap_manager; + m.setup(cc->desktop); - switch (cc->state) { - case SP_CONNECTOR_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ - case SP_CONNECTOR_CONTEXT_IDLE: - { - if ( cc->npoints == 0 ) { - cc_clear_active_conn(cc); + 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")); + SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector")); - /* Set start anchor */ - /* Create green anchor */ - Geom::Point p = event_dt; + /* Set start anchor */ + /* Create green anchor */ + Geom::Point p = event_dt; - // Test whether we clicked on a connection point - cc->sid = conn_pt_handle_test(cc, p); + // Test whether we clicked on a connection point + cc->sid = conn_pt_handle_test(cc, p); + + if (!cc->sid) { + // This is the first point, so just snap it to the grid + // as there's no other points to go off. + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + } + spcc_connector_set_initial_point(cc, p); - if (!cc->sid) { - // This is the first point, so just snap it to the grid - // as there's no other points to go off. - m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); } - 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.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + spcc_connector_set_subsequent_point(cc, p); + spcc_connector_finish_segment(cc, p); + // Test whether we clicked on a connection point + cc->eid = conn_pt_handle_test(cc, p); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + break; } - cc->state = SP_CONNECTOR_CONTEXT_DRAGGING; - ret = TRUE; - break; + case SP_CONNECTOR_CONTEXT_CLOSE: + { + g_warning("Button down in CLOSE state"); + break; + } + default: + break; } - case SP_CONNECTOR_CONTEXT_DRAGGING: - { - // This is the second click of a connector creation. - m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + } 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); - spcc_connector_set_subsequent_point(cc, p); - spcc_connector_finish_segment(cc, p); - // Test whether we clicked on a connection point - cc->eid = conn_pt_handle_test(cc, p); - if (cc->npoints != 0) { - spcc_connector_finish(cc); - } - cc_set_active_conn(cc, cc->newconn); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); - ret = TRUE; - break; + + // Don't set ret to TRUE, so we drop through to the + // parent handler which will open the context menu. } - case SP_CONNECTOR_CONTEXT_CLOSE: - { - g_warning("Button down in CLOSE state"); - break; + else if (cc->npoints != 0) { + spcc_connector_finish(cc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; } - 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); + } + else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE ) + { + if ( bevent.button == 1 && !event_context->space_panning ) + { + // Initialize variables in case of dragging - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - // 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; - sp_event_context_discard_delayed_snap_event(event_context); - ret = TRUE; + if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) { + return TRUE; + } + + cc->xp = bevent.x; + cc->yp = bevent.y; + cc->within_tolerance = true; + + ConnectionPointMap::iterator const& active_knot_it = cc->connpthandles.find( cc->active_handle ); + + switch (cc->state) + { + case SP_CONNECTOR_CONTEXT_IDLE: + if ( active_knot_it != cc->connpthandles.end() ) + { + // We do not allow selecting and, thereby, moving default knots + if ( active_knot_it->second.type != ConnPointDefault) + { + if (cc->selected_handle != cc->active_handle) + { + if ( cc->selected_handle ) + cc_deselect_handle( cc->selected_handle ); + cc->selected_handle = cc->active_handle; + cc_select_handle( cc->selected_handle ); + } + } + else + // Just ignore the default connection point + return FALSE; + } + else + if ( cc->selected_handle ) + { + cc_deselect_handle( cc->selected_handle ); + cc->selected_handle = NULL; + } + + if ( cc->selected_handle ) + { + cc->state = SP_CONNECTOR_CONTEXT_DRAGGING; + cc->selection->set( SP_OBJECT( cc->active_shape ) ); + } + + ret = TRUE; + break; + // Dragging valid because of the way we create + // new connection points. + case SP_CONNECTOR_CONTEXT_DRAGGING: + // Do nothing. + ret = TRUE; + break; + } } } return ret; @@ -607,71 +906,94 @@ connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion cons Geom::Point const event_w(mevent.x, mevent.y); - if (connector_within_tolerance) { - gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - if ( Geom::LInfty( event_w - connector_drag_origin_w ) < tolerance ) { + 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) - connector_within_tolerance = false; + 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; - m.setup(dt); + if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ) + { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); - switch (cc->state) { - case SP_CONNECTOR_CONTEXT_DRAGGING: - { - // This is movement during a connector creation. - if ( cc->npoints > 0 ) { - m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); - cc->selection->clear(); - spcc_connector_set_subsequent_point(cc, p); - ret = TRUE; + 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.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, + Inkscape::SNAPSOURCE_HANDLE); + cc->selection->clear(); + spcc_connector_set_subsequent_point(cc, p); + ret = TRUE; + } + break; } - break; - } - case SP_CONNECTOR_CONTEXT_REROUTING: - { - g_assert( SP_IS_PATH(cc->clickeditem)); + case SP_CONNECTOR_CONTEXT_REROUTING: + { + gobble_motion_events(GDK_BUTTON1_MASK); + g_assert( SP_IS_PATH(cc->clickeditem)); - m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, + Inkscape::SNAPSOURCE_HANDLE); - // Update the hidden path - Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem); - Geom::Matrix d2i = i2d.inverse(); - SPPath *path = SP_PATH(cc->clickeditem); - SPCurve *curve = (SP_SHAPE(path))->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_adjust_path(path); + // Update the hidden path + Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem); + Geom::Matrix d2i = i2d.inverse(); + SPPath *path = SP_PATH(cc->clickeditem); + SPCurve *curve = path->original_curve ? path->original_curve : path->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 = SP_SHAPE(path)->curve->copy(); - cc->red_curve->transform(i2d); + // Copy this to the temporary visible path + cc->red_curve = path->original_curve ? + path->original_curve->copy() : path->curve->copy(); + cc->red_curve->transform(i2d); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); - ret = TRUE; - break; + 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: + break; + } + } + else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE ) + { + switch ( cc->state ) + { + case SP_CONNECTOR_CONTEXT_DRAGGING: + sp_knot_set_position(cc->selected_handle, p, 0); + ret = TRUE; + break; + case SP_CONNECTOR_CONTEXT_NEWCONNPOINT: + sp_knot_set_position(cc->selected_handle, p, 0); + ret = TRUE; + break; } - case SP_CONNECTOR_CONTEXT_STOP: - /* This is perfectly valid */ - break; - default: - break; } return ret; @@ -685,61 +1007,106 @@ connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton con SPEventContext *event_context = SP_EVENT_CONTEXT(cc); if ( revent.button == 1 && !event_context->space_panning ) { - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); SPDocument *doc = sp_desktop_document(desktop); SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); + m.setup(desktop); 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.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); - - if (connector_within_tolerance) + if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ) + { + switch (cc->state) { + //case SP_CONNECTOR_CONTEXT_POINT: + case SP_CONNECTOR_CONTEXT_DRAGGING: { + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + + 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); - return TRUE; + // Test whether we clicked on a connection point + cc->eid = conn_pt_handle_test(cc, p); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + break; } - // 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 - cc->eid = conn_pt_handle_test(cc, p); - if (cc->npoints != 0) { - spcc_connector_finish(cc); + case SP_CONNECTOR_CONTEXT_REROUTING: + { + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + cc_connector_rerouting_finish(cc, &p); + + sp_document_ensure_up_to_date(doc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + return TRUE; + break; } - cc_set_active_conn(cc, cc->newconn); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); - break; + case SP_CONNECTOR_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + break; + default: + break; } - case SP_CONNECTOR_CONTEXT_REROUTING: + ret = TRUE; + } + else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE ) + { + switch ( cc->state ) { - m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); - cc_connector_rerouting_finish(cc, &p); + case SP_CONNECTOR_CONTEXT_DRAGGING: - sp_document_ensure_up_to_date(doc); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); - return TRUE; - break; + if (!cc->within_tolerance) + { + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + sp_knot_set_position(cc->selected_handle, p, 0); + ConnectionPoint& cp = cc->connpthandles[cc->selected_handle]; + cp.pos = p * sp_item_dt2i_affine(cc->active_shape); + cc->active_shape->avoidRef->updateConnectionPoint(cp); + } + + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + break; + + + case SP_CONNECTOR_CONTEXT_NEWCONNPOINT: + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + + sp_knot_set_position(cc->selected_handle, p, 0); + + ConnectionPoint cp; + cp.type = ConnPointUserDefined; + cp.pos = p * sp_item_dt2i_affine(cc->active_shape); + cp.dir = Avoid::ConnDirAll; + g_object_unref(cc->selected_handle); + cc->active_shape->avoidRef->addConnectionPoint(cp); + sp_document_ensure_up_to_date(doc); + for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it) + if (it->second.type == ConnPointUserDefined && it->second.id == cp.id) + { + cc->selected_handle = it->first; + break; + } + cc_select_handle( cc->selected_handle ); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + break; } - case SP_CONNECTOR_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ - break; - default: - break; } - ret = TRUE; } + return ret; } @@ -749,43 +1116,137 @@ connector_handle_key_press(SPConnectorContext *const cc, guint const keyval) { gint ret = FALSE; /* fixme: */ - switch (keyval) { - case GDK_Return: - case GDK_KP_Enter: - if (cc->npoints != 0) { - spcc_connector_finish(cc); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); - ret = TRUE; - } - break; - case GDK_Escape: - if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { + if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ) + { + switch (keyval) { + case GDK_Return: + case GDK_KP_Enter: + if (cc->npoints != 0) { + spcc_connector_finish(cc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + break; + case GDK_Escape: + if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - SPDocument *doc = sp_desktop_document(desktop); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); - cc_connector_rerouting_finish(cc, NULL); + cc_connector_rerouting_finish(cc, NULL); - sp_document_undo(doc); + sp_document_undo(doc); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); - 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; - sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); - spcc_reset_colors(cc); - ret = TRUE; - } - break; - default: - break; + 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; + } + } + else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE ) + { + switch ( cc->state ) + { + case SP_CONNECTOR_CONTEXT_DRAGGING: + if ( keyval == GDK_Escape ) + { + // Cancel connection point dragging + + // Obtain original position + ConnectionPoint const& cp = cc->connpthandles[cc->selected_handle]; + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + const Geom::Matrix& i2doc = sp_item_i2doc_affine(cc->active_shape); + sp_knot_set_position(cc->selected_handle, cp.pos * i2doc * desktop->doc2dt(), 0); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE, + _("Connection point drag cancelled.")); + ret = TRUE; + } + else if ( keyval == GDK_Return || keyval == GDK_KP_Enter ) + { + // Put connection point at current position + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + Geom::Point p = cc->selected_handle->pos; +// SPEventContext* event_context = SP_EVENT_CONTEXT( cc ); + + if (!cc->within_tolerance) + { + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + sp_knot_set_position(cc->selected_handle, p, 0); + ConnectionPoint& cp = cc->connpthandles[cc->selected_handle]; + cp.pos = p * sp_item_dt2i_affine(cc->active_shape); + cc->active_shape->avoidRef->updateConnectionPoint(cp); + } + + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + break; + case SP_CONNECTOR_CONTEXT_NEWCONNPOINT: + if ( keyval == GDK_Escape ) + { + // Just destroy the knot + g_object_unref( cc->selected_handle ); + cc->selected_handle = NULL; + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + else if ( keyval == GDK_Return || keyval == GDK_KP_Enter ) + { + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + Geom::Point p = cc->selected_handle->pos; + + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + + sp_knot_set_position(cc->selected_handle, p, 0); + + ConnectionPoint cp; + cp.type = ConnPointUserDefined; + cp.pos = p * sp_item_dt2i_affine(cc->active_shape); + cp.dir = Avoid::ConnDirAll; + g_object_unref(cc->selected_handle); + cc->active_shape->avoidRef->addConnectionPoint(cp); + sp_document_ensure_up_to_date(doc); + for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it) + if (it->second.type == ConnPointUserDefined && it->second.id == cp.id) + { + cc->selected_handle = it->first; + break; + } + cc_select_handle( cc->selected_handle ); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + + break; + case SP_CONNECTOR_CONTEXT_IDLE: + if ( keyval == GDK_Delete && cc->selected_handle ) + { + cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]); + cc->selected_handle = NULL; + ret = TRUE; + } + + break; + } } + return ret; } @@ -818,7 +1279,7 @@ cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p } } cc->clickeditem->setHidden(false); - sp_conn_adjust_path(SP_PATH(cc->clickeditem)); + sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem)); cc->clickeditem->updateRepr(); sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Reroute connector")); @@ -863,25 +1324,20 @@ spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point co if (!cc->newConnRef) { Avoid::Router *router = sp_desktop_document(dt)->router; - cc->newConnRef = new Avoid::ConnRef(router, 0, src, dst); - cc->newConnRef->updateEndPoint(Avoid::VertID::src, src); + 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); } - cc->newConnRef->updateEndPoint(Avoid::VertID::tar, dst); - + // Set new endpoint. + cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst); + // Immediately generate new routes for connector. cc->newConnRef->makePathInvalid(); - cc->newConnRef->generatePath(src, dst); - - Avoid::PolyLine route = cc->newConnRef->route(); - cc->newConnRef->calcRouteDist(); - - cc->red_curve->reset(); - Geom::Point pt(route.ps[0].x, route.ps[0].y); - cc->red_curve->moveto(pt); - - for (int i = 1; i < route.pn; ++i) { - Geom::Point p(route.ps[i].x, route.ps[i].y); - cc->red_curve->lineto(p); - } + 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); } @@ -953,14 +1409,13 @@ spcc_flush_white(SPConnectorContext *cc, SPCurve *gc) /* Attach repr */ cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); - cc->selection->set(repr); - Inkscape::GC::release(repr); cc->newconn->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse(); - cc->newconn->updateRepr(); bool connection = false; sp_object_setAttribute(cc->newconn, "inkscape:connector-type", - "polyline", false); + cc->isOrthogonal ? "orthogonal" : "polyline", false); + sp_object_setAttribute(cc->newconn, "inkscape:connector-curvature", + Glib::Ascii::dtostr(cc->curvature).c_str(), false); if (cc->sid) { sp_object_setAttribute(cc->newconn, "inkscape:connection-start", @@ -974,19 +1429,27 @@ spcc_flush_white(SPConnectorContext *cc, SPCurve *gc) cc->eid, false); connection = true; } + // Process pending updates. cc->newconn->updateRepr(); + sp_document_ensure_up_to_date(doc); + if (connection) { // Adjust endpoints to shape edge. - sp_conn_adjust_path(SP_PATH(cc->newconn)); + sp_conn_reroute_path_immediate(SP_PATH(cc->newconn)); + cc->newconn->updateRepr(); } - cc->newconn->updateRepr(); + + // 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(); - /* Flush pending updates */ sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector")); - sp_document_ensure_up_to_date(doc); } @@ -1036,16 +1499,16 @@ cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot) gboolean consumed = FALSE; + gchar* knot_tip = knot->tip ? knot->tip : cc->knot_tip; switch (event->type) { case GDK_ENTER_NOTIFY: sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE); cc->active_handle = knot; - - if (knot->tip) + if (knot_tip) { knot->desktop->event_context->defaultMessageContext()->set( - Inkscape::NORMAL_MESSAGE, knot->tip); + Inkscape::NORMAL_MESSAGE, knot_tip); } consumed = TRUE; @@ -1055,7 +1518,7 @@ cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot) cc->active_handle = NULL; - if (knot->tip) { + if (knot_tip) { knot->desktop->event_context->defaultMessageContext()->clear(); } @@ -1101,7 +1564,7 @@ endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc) } // Show the red path for dragging. - cc->red_curve = SP_PATH(cc->clickeditem)->curve->copy(); + cc->red_curve = SP_PATH(cc->clickeditem)->original_curve ? SP_PATH(cc->clickeditem)->original_curve->copy() : SP_PATH(cc->clickeditem)->curve->copy(); Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem); cc->red_curve->transform(i2d); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); @@ -1120,38 +1583,9 @@ endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc) return consumed; } - -static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item) +static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp) { - g_assert(item != NULL ); - - 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 = SP_OBJECT_REPR(item); - 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); - } - - - // Set center connection point. - if ( cc->connpthandle == NULL ) { - SPKnot *knot = sp_knot_new(cc->desktop, - _("<b>Connection point</b>: click or drag to create a new connector")); + SPKnot *knot = sp_knot_new(desktop, 0); knot->setShape(SP_KNOT_SHAPE_SQUARE); knot->setSize(8); @@ -1159,26 +1593,127 @@ static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item) knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); 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. + // 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; gtk_signal_connect(GTK_OBJECT(knot->item), "event", GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot); + sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos(cp.type, cp.id) * desktop->doc2dt(), 0); + sp_knot_show(knot); + cphandles[knot] = cp; +} + +static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item) +{ + g_assert(item != NULL ); + + std::map<int, ConnectionPoint>* connpts = &item->avoidRef->connection_points; + + 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); + } - cc->connpthandle = knot; + // Listen in case the active shape changes + cc->active_shape_repr = SP_OBJECT_REPR(item); + 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); + } + + + // Set the connection points. + if ( cc->connpthandles.size() ) + // destroy the old list + while (! cc->connpthandles.empty() ) + { + g_object_unref(cc->connpthandles.begin()->first); + cc->connpthandles.erase(cc->connpthandles.begin()); + } + // build the new one + if ( connpts->size() ) + for (std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it) + cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second); + + // Also add default connection points + // For now, only centre default connection point will + // be available + ConnectionPoint centre; + centre.type = ConnPointDefault; + centre.id = ConnPointPosCC; + cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, centre); } + else + { + // The active shape didn't change + // Update only the connection point knots + // Ensure the item's connection_points map + // has been updated + sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item)); - Geom::OptRect bbox = sp_item_bbox_desktop(cc->active_shape); - if (bbox) { - Geom::Point center = bbox->midpoint(); - sp_knot_set_position(cc->connpthandle, center, 0); - sp_knot_show(cc->connpthandle); - } else { - sp_knot_hide(cc->connpthandle); + std::set<int> seen; + for ( ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end() ;) + { + bool removed = false; + if ( it->second.type == ConnPointUserDefined ) + { + std::map<int, ConnectionPoint>::iterator p = connpts->find(it->second.id); + if (p != connpts->end()) + { + if ( it->second != p->second ) + // Connection point position has changed + // Update knot position + sp_knot_set_position(it->first, + item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0); + seen.insert(it->second.id); + sp_knot_show(it->first); + } + else + { + // This connection point does no longer exist, + // remove the knot + ConnectionPointMap::iterator curr = it; + ++it; + g_object_unref( curr->first ); + cc->connpthandles.erase(curr); + removed = true; + } + } + else + { + // It's a default connection point + // Just make sure it's position is correct + sp_knot_set_position(it->first, + item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0); + sp_knot_show(it->first); + + } + if ( !removed ) + ++it; + } + // Add knots for new connection points. + if (connpts->size()) + for ( std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it ) + if ( seen.find(it->first) == seen.end() ) + // A new connection point has been added + // to the shape. Add a knot for it. + cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second); } } @@ -1188,7 +1723,7 @@ cc_set_active_conn(SPConnectorContext *cc, SPItem *item) { g_assert( SP_IS_PATH(item) ); - SPCurve *curve = SP_SHAPE(SP_PATH(item))->curve; + SPCurve *curve = SP_PATH(item)->original_curve ? SP_PATH(item)->original_curve : SP_PATH(item)->curve; Geom::Matrix i2d = sp_item_i2d_affine(item); if (cc->active_conn == item) @@ -1234,7 +1769,7 @@ cc_set_active_conn(SPConnectorContext *cc, SPItem *item) 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. + // 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; @@ -1271,6 +1806,35 @@ cc_set_active_conn(SPConnectorContext *cc, SPItem *item) sp_knot_show(cc->endpt_handle[1]); } +void cc_create_connection_point(SPConnectorContext* 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; + } +} + +void cc_remove_connection_point(SPConnectorContext* cc) +{ + if (cc->selected_handle && cc->state == SP_CONNECTOR_CONTEXT_IDLE ) + { + cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]); + cc->selected_handle = NULL; + } +} static bool cc_item_is_shape(SPItem *item) { @@ -1296,7 +1860,7 @@ bool cc_item_is_connector(SPItem *item) { if (SP_IS_PATH(item)) { if (SP_PATH(item)->connEndPair.isAutoRoutingConn()) { - g_assert( !(SP_SHAPE(item)->curve->is_closed()) ); + g_assert( SP_PATH(item)->original_curve ? !(SP_PATH(item)->original_curve->is_closed()) : !(SP_PATH(item)->curve->is_closed()) ); return true; } } @@ -1394,7 +1958,7 @@ shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, g_assert(data); SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data); - // Look for changes than result in onscreen movement. + // 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")) @@ -1409,6 +1973,12 @@ shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, cc_set_active_conn(cc, cc->active_conn); } } + else + if ( !strcmp(name, "inkscape:connection-points") ) + if (repr == cc->active_shape_repr) + // The connection points of the active shape + // have changed. Update them. + cc_set_active_shape(cc, cc->active_shape); } diff --git a/src/connector-context.h b/src/connector-context.h index d67e12b81..640a03aae 100644 --- a/src/connector-context.h +++ b/src/connector-context.h @@ -19,7 +19,8 @@ #include <display/display-forward.h> #include <2geom/point.h> #include "libavoid/connector.h" - +#include "connection-points.h" +#include <glibmm/i18n.h> #define SP_TYPE_CONNECTOR_CONTEXT (sp_connector_context_get_type()) #define SP_CONNECTOR_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_CONNECTOR_CONTEXT, SPConnectorContext)) @@ -38,9 +39,18 @@ enum { SP_CONNECTOR_CONTEXT_DRAGGING, SP_CONNECTOR_CONTEXT_CLOSE, SP_CONNECTOR_CONTEXT_STOP, - SP_CONNECTOR_CONTEXT_REROUTING + SP_CONNECTOR_CONTEXT_REROUTING, + SP_CONNECTOR_CONTEXT_NEWCONNPOINT +}; + +enum { + SP_CONNECTOR_CONTEXT_DRAWING_MODE, + SP_CONNECTOR_CONTEXT_EDITING_MODE }; +static char* cc_knot_tips[] = { _("<b>Connection point</b>: click or drag to create a new connector"), + _("<b>Connection point</b>: click to select, drag to move") }; +typedef std::map<SPKnot *, ConnectionPoint> ConnectionPointMap; struct SPConnectorContext : public SPEventContext { Inkscape::Selection *selection; @@ -48,10 +58,14 @@ struct SPConnectorContext : public SPEventContext { /** \invar npoints in {0, 2}. */ gint npoints; - + /* The tool mode can be connector drawing or + connection points editing. + */ unsigned int mode : 1; unsigned int state : 4; + gchar* knot_tip; + // Red curve SPCanvasItem *red_bpath; SPCurve *red_curve; @@ -63,6 +77,8 @@ struct SPConnectorContext : public SPEventContext { // The new connector SPItem *newconn; Avoid::ConnRef *newConnRef; + gdouble curvature; + bool isOrthogonal; // The active shape SPItem *active_shape; @@ -78,10 +94,13 @@ struct SPConnectorContext : public SPEventContext { // The activehandle SPKnot *active_handle; + // The selected handle, used in editing mode + SPKnot *selected_handle; + SPItem *clickeditem; SPKnot *clickedhandle; - SPKnot *connpthandle; + ConnectionPointMap connpthandles; SPKnot *endpt_handle[2]; guint endpt_handler_id[2]; gchar *sid; @@ -93,7 +112,10 @@ struct SPConnectorContextClass : public SPEventContextClass { }; GType sp_connector_context_get_type(); +void sp_connector_context_switch_mode(SPEventContext* ec, unsigned int newMode); void cc_selection_set_avoid(bool const set_ignore); +void cc_create_connection_point(SPConnectorContext* cc); +void cc_remove_connection_point(SPConnectorContext* cc); bool cc_item_is_connector(SPItem *item); diff --git a/src/context-fns.cpp b/src/context-fns.cpp index 8e4b6384c..67a7d6baa 100644 --- a/src/context-fns.cpp +++ b/src/context-fns.cpp @@ -131,12 +131,12 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item Inkscape::SnappedPoint s[2]; /* Try to snap p[0] (the opposite corner) along the constraint vector */ - s[0] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[0] - p[1]), false); + s[0] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p[0], Inkscape::SNAPSOURCE_HANDLE), + Inkscape::Snapper::ConstraintLine(p[0] - p[1])); /* Try to snap p[1] (the dragged corner) along the constraint vector */ - s[1] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false); + s[1] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_HANDLE), + Inkscape::Snapper::ConstraintLine(p[1] - p[0])); /* Choose the best snap and update points accordingly */ if (s[0].getSnapDistance() < s[1].getSnapDistance()) { @@ -156,8 +156,8 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* Our origin is the opposite corner. Snap the drag point along the constraint vector */ p[0] = center; - snappoint = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE, - Inkscape::Snapper::ConstraintLine(p[1] - p[0]), false); + snappoint = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_HANDLE), + Inkscape::Snapper::ConstraintLine(p[1] - p[0])); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } @@ -174,8 +174,8 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item Inkscape::SnappedPoint s[2]; - s[0] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]), Inkscape::SNAPSOURCE_HANDLE); - s[1] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]), Inkscape::SNAPSOURCE_HANDLE); + s[0] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p[0], Inkscape::SNAPSOURCE_HANDLE)); + s[1] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p[1], Inkscape::SNAPSOURCE_HANDLE)); if (s[0].getSnapDistance() < s[1].getSnapDistance()) { if (s[0].getSnapped()) { @@ -196,7 +196,7 @@ Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item /* There's no constraint on the corner point, so just snap it to anything */ p[0] = center; p[1] = pt; - snappoint = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(pt), Inkscape::SNAPSOURCE_HANDLE); + snappoint = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_HANDLE)); if (snappoint.getSnapped()) { p[1] = snappoint.getPoint(); } diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index c8782051b..2afcd6109 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -44,6 +44,7 @@ #include "desktop-style.h" #include "svg/svg-icc-color.h" +#include "svg/svg-device-color.h" #include "box3d-side.h" /** @@ -432,6 +433,8 @@ objects_query_fillstroke (GSList *objects, SPStyle *style_res, bool const isfill paint_res->set = TRUE; SVGICCColor* iccColor = 0; + SVGDeviceColor* devColor = 0; + bool iccSeen = false; gfloat c[4]; c[0] = c[1] = c[2] = c[3] = 0.0; @@ -529,6 +532,23 @@ objects_query_fillstroke (GSList *objects, SPStyle *style_res, bool const isfill c[1] += d[1]; c[2] += d[2]; c[3] += SP_SCALE24_TO_FLOAT (isfill? style->fill_opacity.value : style->stroke_opacity.value); + + // average device color + unsigned int it; + if (i==objects /*if this is the first object in the GList*/ + && paint->value.color.device){ + devColor = new SVGDeviceColor(*paint->value.color.device); + for (it=0; it < paint->value.color.device->colors.size(); it++){ + devColor->colors[it] = 0; + } + } + + if (devColor && paint->value.color.device && paint->value.color.device->type == devColor->type){ + for (it=0; it < paint->value.color.device->colors.size(); it++){ + devColor->colors[it] += paint->value.color.device->colors[it]; + } + } + num ++; } @@ -568,6 +588,14 @@ objects_query_fillstroke (GSList *objects, SPStyle *style_res, bool const isfill paint_res->value.color.icc = tmp; } + // divide and store the device-color + if (devColor){ + for (unsigned int it=0; it < devColor->colors.size(); it++){ + devColor->colors[it] /= num; + } + paint_res->value.color.device = devColor; + } + if (num > 1) { if (same_color) return QUERY_STYLE_MULTIPLE_SAME; diff --git a/src/desktop.cpp b/src/desktop.cpp index e4e2ed7bc..69aec65e0 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -462,6 +462,9 @@ void SPDesktop::displayModeToggle() { _setDisplayMode(Inkscape::RENDERMODE_OUTLINE); break; case Inkscape::RENDERMODE_OUTLINE: + _setDisplayMode(Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW); + break; + case Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW: default: _setDisplayMode(Inkscape::RENDERMODE_NORMAL); } @@ -606,8 +609,10 @@ SPDesktop::change_document (SPDocument *theDocument) Gtk::Window *parent = this->getToplevel(); g_assert(parent != NULL); SPDesktopWidget *dtw = (SPDesktopWidget *) parent->get_data("desktopwidget"); - if (dtw) dtw->desktop = this; - sp_desktop_widget_update_namedview(dtw); + if (dtw) { + dtw->desktop = this; + } + dtw->updateNamedview(); _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); _document_replaced_signal.emit (this, theDocument); diff --git a/src/desktop.h b/src/desktop.h index 2d3c9b6e4..00f6cfdd5 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -38,10 +38,7 @@ #include "display/rendermode.h" #include "display/snap-indicator.h" -class NRRect; class SPCSSAttr; -struct _GtkWidget; -typedef struct _GtkWidget GtkWidget; struct SPCanvas; struct SPCanvasItem; struct SPCanvasGroup; @@ -206,6 +203,9 @@ struct SPDesktop : public Inkscape::UI::View::View void setDisplayModeOutline() { _setDisplayMode(Inkscape::RENDERMODE_OUTLINE); } + void setDisplayModePrintColorsPreview() { + _setDisplayMode(Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW); + } void displayModeToggle(); Inkscape::RenderMode _display_mode; Inkscape::RenderMode getMode() const { return _display_mode; } diff --git a/src/display/canvas-axonomgrid.cpp b/src/display/canvas-axonomgrid.cpp index a92e7cf5b..ee05cd01c 100644 --- a/src/display/canvas-axonomgrid.cpp +++ b/src/display/canvas-axonomgrid.cpp @@ -359,9 +359,9 @@ CanvasAxonomGrid::readRepr() } if ( (value = repr->attribute("snapvisiblegridlinesonly")) ) { - g_assert(snapper != NULL); - snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0); - } + g_assert(snapper != NULL); + snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0); + } for (GSList *l = canvasitems; l != NULL; l = l->next) { sp_canvas_item_request_update ( SP_CANVAS_ITEM(l->data) ); @@ -671,9 +671,9 @@ CanvasAxonomGridSnapper::CanvasAxonomGridSnapper(CanvasAxonomGrid *grid, SnapMan */ Geom::Coord CanvasAxonomGridSnapper::getSnapperTolerance() const { - SPDesktop const *dt = _snapmanager->getDesktop(); - double const zoom = dt ? dt->current_zoom() : 1; - return _snapmanager->snapprefs.getGridTolerance() / zoom; + SPDesktop const *dt = _snapmanager->getDesktop(); + double const zoom = dt ? dt->current_zoom() : 1; + return _snapmanager->snapprefs.getGridTolerance() / zoom; } bool CanvasAxonomGridSnapper::getSnapperAlwaysSnap() const @@ -694,22 +694,22 @@ CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const double spacing_v; if (getSnapVisibleOnly()) { - // Only snapping to visible grid lines - spacing_h = grid->spacing_ylines; // this is the spacing of the visible grid lines measured in screen pixels - spacing_v = grid->lyw; // vertical - // convert screen pixels to px - // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary - SPDesktop const *dt = _snapmanager->getDesktop(); - if (dt) { - spacing_h /= dt->current_zoom(); - spacing_v /= dt->current_zoom(); - } - } else { - // Snapping to any grid line, whether it's visible or not - spacing_h = grid->lengthy /(grid->tan_angle[X] + grid->tan_angle[Z]); - spacing_v = grid->lengthy; - - } + // Only snapping to visible grid lines + spacing_h = grid->spacing_ylines; // this is the spacing of the visible grid lines measured in screen pixels + spacing_v = grid->lyw; // vertical + // convert screen pixels to px + // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary + SPDesktop const *dt = _snapmanager->getDesktop(); + if (dt) { + spacing_h /= dt->current_zoom(); + spacing_v /= dt->current_zoom(); + } + } else { + // Snapping to any grid line, whether it's visible or not + spacing_h = grid->lengthy /(grid->tan_angle[X] + grid->tan_angle[Z]); + spacing_v = grid->lengthy; + + } // In an axonometric grid, any point will be surrounded by 6 grid lines: // - 2 vertical grid lines, one left and one right from the point @@ -746,18 +746,18 @@ CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const Geom::Point p_x(0, y_proj_along_x_max); Geom::Line line_x(p_x, p_x + vers_x); Geom::Point p_z(0, y_proj_along_z_max); - Geom::Line line_z(p_z, p_z + vers_z); + Geom::Line line_z(p_z, p_z + vers_z); Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default - try - { - inters = Geom::intersection(line_x, line_z); - } - catch (Geom::InfiniteSolutions e) - { - // We're probably dealing with parallel lines; this is useless! - return s; - } + try + { + inters = Geom::intersection(line_x, line_z); + } + catch (Geom::InfiniteSolutions e) + { + // We're probably dealing with parallel lines; this is useless! + return s; + } // Determine which half of the parallelogram to use bool use_left_half = true; @@ -765,7 +765,7 @@ CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const if (inters) { Geom::Point inters_pt = line_x.pointAt((*inters).ta); - use_left_half = (p[Geom::X] - grid->origin[Geom::X]) < inters_pt[Geom::X]; + use_left_half = (p[Geom::X] - grid->origin[Geom::X]) < inters_pt[Geom::X]; use_right_half = !use_left_half; } @@ -786,16 +786,16 @@ CanvasAxonomGridSnapper::_getSnapLines(Geom::Point const &p) const return s; } -void CanvasAxonomGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, Geom::Point const point_on_line) const +void CanvasAxonomGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, Geom::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.grid_lines.push_back(dummy); } -void CanvasAxonomGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const +void CanvasAxonomGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const { - SnappedPoint dummy = SnappedPoint(snapped_point, source, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); - sc.points.push_back(dummy); + SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + sc.points.push_back(dummy); } bool CanvasAxonomGridSnapper::ThisSnapperMightSnap() const diff --git a/src/display/canvas-axonomgrid.h b/src/display/canvas-axonomgrid.h index e36804d7c..4b1cd4834 100644 --- a/src/display/canvas-axonomgrid.h +++ b/src/display/canvas-axonomgrid.h @@ -78,8 +78,8 @@ public: private: LineList _getSnapLines(Geom::Point const &p) const; - void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const; + void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const; CanvasAxonomGrid *grid; }; diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp index 5037c0375..3532c504a 100644 --- a/src/display/canvas-grid.cpp +++ b/src/display/canvas-grid.cpp @@ -635,9 +635,9 @@ CanvasXYGrid::readRepr() } if ( (value = repr->attribute("snapvisiblegridlinesonly")) ) { - g_assert(snapper != NULL); - snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0); - } + g_assert(snapper != NULL); + snapper->setSnapVisibleOnly(strcmp(value,"false") != 0 && strcmp(value, "0") != 0); + } for (GSList *l = canvasitems; l != NULL; l = l->next) { sp_canvas_item_request_update ( SP_CANVAS_ITEM(l->data) ); @@ -972,9 +972,9 @@ CanvasXYGridSnapper::CanvasXYGridSnapper(CanvasXYGrid *grid, SnapManager *sm, Ge */ Geom::Coord CanvasXYGridSnapper::getSnapperTolerance() const { - SPDesktop const *dt = _snapmanager->getDesktop(); - double const zoom = dt ? dt->current_zoom() : 1; - return _snapmanager->snapprefs.getGridTolerance() / zoom; + SPDesktop const *dt = _snapmanager->getDesktop(); + double const zoom = dt ? dt->current_zoom() : 1; + return _snapmanager->snapprefs.getGridTolerance() / zoom; } bool CanvasXYGridSnapper::getSnapperAlwaysSnap() const @@ -993,20 +993,20 @@ CanvasXYGridSnapper::_getSnapLines(Geom::Point const &p) const for (unsigned int i = 0; i < 2; ++i) { - double spacing; - - if (getSnapVisibleOnly()) { - // Only snapping to visible grid lines - spacing = grid->sw[i]; // this is the spacing of the visible grid lines measured in screen pixels - // convert screen pixels to px - // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary - SPDesktop const *dt = _snapmanager->getDesktop(); - if (dt) { - spacing /= dt->current_zoom(); - } + double spacing; + + if (getSnapVisibleOnly()) { + // Only snapping to visible grid lines + spacing = grid->sw[i]; // this is the spacing of the visible grid lines measured in screen pixels + // convert screen pixels to px + // FIXME: after we switch to snapping dist in screen pixels, this will be unnecessary + SPDesktop const *dt = _snapmanager->getDesktop(); + if (dt) { + spacing /= dt->current_zoom(); + } } else { - // Snapping to any grid line, whether it's visible or not - spacing = grid->spacing[i]; + // Snapping to any grid line, whether it's visible or not + spacing = grid->spacing[i]; } Geom::Coord rounded; @@ -1024,16 +1024,16 @@ CanvasXYGridSnapper::_getSnapLines(Geom::Point const &p) const return s; } -void CanvasXYGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, Geom::Point const point_on_line) const +void CanvasXYGridSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, Geom::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.grid_lines.push_back(dummy); } -void CanvasXYGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const +void CanvasXYGridSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const { - SnappedPoint dummy = SnappedPoint(snapped_point, source, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); - sc.points.push_back(dummy); + SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + sc.points.push_back(dummy); } /** diff --git a/src/display/canvas-grid.h b/src/display/canvas-grid.h index 58cfbf735..daf28c15c 100644 --- a/src/display/canvas-grid.h +++ b/src/display/canvas-grid.h @@ -166,8 +166,8 @@ public: private: LineList _getSnapLines(Geom::Point const &p) const; - void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const; + void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, const Geom::Point point_on_line) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const; CanvasXYGrid *grid; }; diff --git a/src/display/nr-arena-glyphs.cpp b/src/display/nr-arena-glyphs.cpp index 429f1ed32..db0922915 100644 --- a/src/display/nr-arena-glyphs.cpp +++ b/src/display/nr-arena-glyphs.cpp @@ -444,6 +444,7 @@ nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPi SPStyle const *style = ggroup->style; guint ret = item->state; + bool print_colors_preview = (item->arena->rendermode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW); if (item->arena->rendermode == Inkscape::RENDERMODE_OUTLINE) { @@ -511,6 +512,10 @@ nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPi } else { rgba = style->fill.value.color.toRGBA32( SP_SCALE24_TO_FLOAT(style->fill_opacity.value) ); } + + if (print_colors_preview) + nr_arena_separate_color_plates(&rgba); + nr_blit_pixblock_mask_rgba32(pb, &m, rgba); pb->empty = FALSE; } @@ -551,6 +556,10 @@ nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPi } else { rgba = style->stroke.value.color.toRGBA32( SP_SCALE24_TO_FLOAT(style->stroke_opacity.value) ); } + + if (print_colors_preview) + nr_arena_separate_color_plates(&rgba); + nr_blit_pixblock_mask_rgba32(pb, &m, rgba); pb->empty = FALSE; } else { diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp index bdab5b479..b80df7273 100644 --- a/src/display/nr-arena-item.cpp +++ b/src/display/nr-arena-item.cpp @@ -312,7 +312,9 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area NRPixBlock *pb, unsigned int flags) { bool outline = (item->arena->rendermode == Inkscape::RENDERMODE_OUTLINE); - bool filter = (item->arena->rendermode == Inkscape::RENDERMODE_NORMAL); + bool filter = (item->arena->rendermode != Inkscape::RENDERMODE_OUTLINE && + item->arena->rendermode != Inkscape::RENDERMODE_NO_FILTERS); + bool print_colors = (item->arena->rendermode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW); nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), diff --git a/src/display/nr-arena-shape.cpp b/src/display/nr-arena-shape.cpp index 96ea76cbe..e2a9e9580 100644 --- a/src/display/nr-arena-shape.cpp +++ b/src/display/nr-arena-shape.cpp @@ -35,6 +35,7 @@ #include "display/nr-filter.h" #include <typeinfo> #include <cairo.h> +#include "preferences.h" #include <glib.h> #include "svg/svg.h" @@ -831,7 +832,6 @@ cairo_arena_shape_render_stroke(NRArenaItem *item, NRRectL *area, NRPixBlock *pb pb->empty = FALSE; } - /** * Renders the item. Markers are just composed into the parent buffer. */ @@ -844,6 +844,7 @@ nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock if (!shape->style) return item->state; bool outline = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_OUTLINE); + bool print_colors_preview = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW); if (outline) { // cairo outline rendering @@ -874,6 +875,7 @@ nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock } SPStyle const *style = shape->style; + if (shape->fill_shp) { NRPixBlock m; guint32 rgba; @@ -893,12 +895,18 @@ nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock if (shape->_fill.paint.type() == NRArenaShape::Paint::NONE) { // do not render fill in any way } else if (shape->_fill.paint.type() == NRArenaShape::Paint::COLOR) { + + const SPColor* fill_color = &shape->_fill.paint.color(); if ( item->render_opacity ) { - rgba = shape->_fill.paint.color().toRGBA32( shape->_fill.opacity * - SP_SCALE24_TO_FLOAT(style->opacity.value) ); + rgba = fill_color->toRGBA32( shape->_fill.opacity * + SP_SCALE24_TO_FLOAT(style->opacity.value) ); } else { - rgba = shape->_fill.paint.color().toRGBA32( shape->_fill.opacity ); + rgba = fill_color->toRGBA32( shape->_fill.opacity ); } + + if (print_colors_preview) + nr_arena_separate_color_plates(&rgba); + nr_blit_pixblock_mask_rgba32(pb, &m, rgba); pb->empty = FALSE; } else if (shape->_fill.paint.type() == NRArenaShape::Paint::SERVER) { @@ -929,14 +937,19 @@ nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock nr_pixblock_render_shape_mask_or(m, shape->stroke_shp); m.empty = FALSE; - if ( item->render_opacity ) { - rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity * - SP_SCALE24_TO_FLOAT(style->opacity.value) ); - } else { - rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity ); - } - nr_blit_pixblock_mask_rgba32(pb, &m, rgba); - pb->empty = FALSE; + const SPColor* stroke_color = &shape->_stroke.paint.color(); + if ( item->render_opacity ) { + rgba = stroke_color->toRGBA32( shape->_stroke.opacity * + SP_SCALE24_TO_FLOAT(style->opacity.value) ); + } else { + rgba = stroke_color->toRGBA32( shape->_stroke.opacity ); + } + + if (print_colors_preview) + nr_arena_separate_color_plates(&rgba); + + nr_blit_pixblock_mask_rgba32(pb, &m, rgba); + pb->empty = FALSE; nr_pixblock_release(&m); diff --git a/src/display/nr-arena.cpp b/src/display/nr-arena.cpp index 74e0f409c..33870a118 100644 --- a/src/display/nr-arena.cpp +++ b/src/display/nr-arena.cpp @@ -18,6 +18,7 @@ #include "nr-filter-types.h" #include <libnr/nr-blit.h> #include "preferences.h" +#include "color.h" static void nr_arena_class_init (NRArenaClass *klass); static void nr_arena_init (NRArena *arena); @@ -181,6 +182,29 @@ nr_arena_set_renderoffscreen (NRArena *arena) } +#define FLOAT_TO_UINT8(f) (int(f*255)) +#define RGBA_R(v) ((v) >> 24) +#define RGBA_G(v) (((v) >> 16) & 0xff) +#define RGBA_B(v) (((v) >> 8) & 0xff) +#define RGBA_A(v) ((v) & 0xff) + +void nr_arena_separate_color_plates(guint32* rgba){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool render_cyan = prefs->getBool("/options/printcolorspreview/cyan", true); + bool render_magenta = prefs->getBool("/options/printcolorspreview/magenta", true); + bool render_yellow = prefs->getBool("/options/printcolorspreview/yellow", true); + bool render_black = prefs->getBool("/options/printcolorspreview/black", true); + + float rgb_v[3]; + float cmyk_v[4]; + sp_color_rgb_to_cmyk_floatv (cmyk_v, RGBA_R(*rgba)/256.0, RGBA_G(*rgba)/256.0, RGBA_B(*rgba)/256.0); + sp_color_cmyk_to_rgb_floatv (rgb_v, render_cyan ? cmyk_v[0] : 0, + render_magenta ? cmyk_v[1] : 0, + render_yellow ? cmyk_v[2] : 0, + render_black ? cmyk_v[3] : 0); + *rgba = (FLOAT_TO_UINT8(rgb_v[0])<<24) + (FLOAT_TO_UINT8(rgb_v[1])<<16) + (FLOAT_TO_UINT8(rgb_v[2])<<8) + 0xff; +} + /* Local Variables: mode:c++ diff --git a/src/display/nr-arena.h b/src/display/nr-arena.h index 1c091b7c7..d2f9dc246 100644 --- a/src/display/nr-arena.h +++ b/src/display/nr-arena.h @@ -64,4 +64,6 @@ void nr_arena_set_renderoffscreen (NRArena *arena); void nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask); +void nr_arena_separate_color_plates(guint32* rgba); + #endif diff --git a/src/display/nr-filter-colormatrix.cpp b/src/display/nr-filter-colormatrix.cpp index 66fb196cb..0b24649a9 100644 --- a/src/display/nr-filter-colormatrix.cpp +++ b/src/display/nr-filter-colormatrix.cpp @@ -2,7 +2,7 @@ * feColorMatrix filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Jasper van de Gronde <th.v.d.gronde@hccnet.nl> * * Copyright (C) 2007 authors diff --git a/src/display/nr-filter-colormatrix.h b/src/display/nr-filter-colormatrix.h index 1c331a5b0..47b454c53 100644 --- a/src/display/nr-filter-colormatrix.h +++ b/src/display/nr-filter-colormatrix.h @@ -5,7 +5,7 @@ * feColorMatrix filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-component-transfer.cpp b/src/display/nr-filter-component-transfer.cpp index 87f87c95a..ab9990360 100644 --- a/src/display/nr-filter-component-transfer.cpp +++ b/src/display/nr-filter-component-transfer.cpp @@ -2,7 +2,7 @@ * feComponentTransfer filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Jasper van de Gronde <th.v.d.gronde@hccnet.nl> * * Copyright (C) 2007 authors diff --git a/src/display/nr-filter-component-transfer.h b/src/display/nr-filter-component-transfer.h index 3d8be272e..eb76bd543 100644 --- a/src/display/nr-filter-component-transfer.h +++ b/src/display/nr-filter-component-transfer.h @@ -5,7 +5,7 @@ * feComponentTransfer filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-convolve-matrix.cpp b/src/display/nr-filter-convolve-matrix.cpp index e9f7e7dfe..fc88102d8 100644 --- a/src/display/nr-filter-convolve-matrix.cpp +++ b/src/display/nr-filter-convolve-matrix.cpp @@ -2,7 +2,7 @@ * feConvolveMatrix filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Jasper van de Gronde <th.v.d.gronde@hccnet.nl> * * Copyright (C) 2007,2009 authors @@ -41,7 +41,7 @@ static inline void convolve2D_XY(unsigned int const x, unsigned int const y, uns unsigned int jEnd = X_UPPER ? width+targetX-x : orderX; for (unsigned int i=iBegin; i<iEnd; i++){ - for (int j=jBegin; j<jEnd; j++){ + for (unsigned int j=jBegin; j<jEnd; j++){ unsigned int index = 4*( x - targetX + j + width*(y - targetY + i) ); unsigned int kernel_index = orderX-j-1 + orderX*(orderY-i-1); double k = PREMULTIPLIED ? kernel[kernel_index] : in_data[index+3] * kernel[kernel_index]; diff --git a/src/display/nr-filter-convolve-matrix.h b/src/display/nr-filter-convolve-matrix.h index d7a04a766..e7416f9cc 100644 --- a/src/display/nr-filter-convolve-matrix.h +++ b/src/display/nr-filter-convolve-matrix.h @@ -5,7 +5,7 @@ * feConvolveMatrix filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-displacement-map.cpp b/src/display/nr-filter-displacement-map.cpp index 869a184ac..4de5e658c 100644 --- a/src/display/nr-filter-displacement-map.cpp +++ b/src/display/nr-filter-displacement-map.cpp @@ -2,7 +2,7 @@ * feDisplacementMap filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-displacement-map.h b/src/display/nr-filter-displacement-map.h index 180030c85..bb15b77a3 100644 --- a/src/display/nr-filter-displacement-map.h +++ b/src/display/nr-filter-displacement-map.h @@ -5,7 +5,7 @@ * feDisplacementMap filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-flood.cpp b/src/display/nr-filter-flood.cpp index 026cbce16..1d804f969 100644 --- a/src/display/nr-filter-flood.cpp +++ b/src/display/nr-filter-flood.cpp @@ -2,15 +2,21 @@ * feFlood filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * * Released under GNU GPL, read the file 'COPYING' for more information */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include "display/nr-filter-flood.h" #include "display/nr-filter-utils.h" +#include "svg/svg-icc-color.h" +#include "svg/svg-color.h" namespace Inkscape { namespace Filters { @@ -26,6 +32,7 @@ FilterFlood::~FilterFlood() {} int FilterFlood::render(FilterSlot &slot, FilterUnits const &/*units*/) { +g_message("rendering feflood"); NRPixBlock *in = slot.get(_input); if (!in) { g_warning("Missing source image for feFlood (in=%d)", _input); @@ -43,12 +50,18 @@ int FilterFlood::render(FilterSlot &slot, FilterUnits const &/*units*/) { true); unsigned char *out_data = NR_PIXBLOCK_PX(out); - unsigned char r,g,b,a; - r = CLAMP_D_TO_U8((color >> 24) % 256); - g = CLAMP_D_TO_U8((color >> 16) % 256); - b = CLAMP_D_TO_U8((color >> 8) % 256); - a = CLAMP_D_TO_U8(opacity*255); + + + r = CLAMP_D_TO_U8((color >> 24) % 256); + g = CLAMP_D_TO_U8((color >> 16) % 256); + b = CLAMP_D_TO_U8((color >> 8) % 256); + a = CLAMP_D_TO_U8(opacity*255); + +#if ENABLE_LCMS + icc_color_to_sRGB(icc, &r, &g, &b); +g_message("result: r:%d g:%d b:%d", r, g, b); +#endif //ENABLE_LCMS for(i=0; i < 4*in_h*in_w; i+=4){ out_data[i]=r; @@ -70,6 +83,10 @@ void FilterFlood::set_opacity(double o) { opacity = o; } +void FilterFlood::set_icc(SVGICCColor *icc_color) { + icc = icc_color; +} + void FilterFlood::area_enlarge(NRRectL &/*area*/, Geom::Matrix const &/*trans*/) { } diff --git a/src/display/nr-filter-flood.h b/src/display/nr-filter-flood.h index 9e6a53abb..98c374bbd 100644 --- a/src/display/nr-filter-flood.h +++ b/src/display/nr-filter-flood.h @@ -5,7 +5,7 @@ * feFlood filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * @@ -15,6 +15,7 @@ #include "display/nr-filter-primitive.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-units.h" +#include "svg/svg-color.h" namespace Inkscape { namespace Filters { @@ -27,11 +28,13 @@ public: virtual void set_opacity(double o); virtual void set_color(guint32 c); + virtual void set_icc(SVGICCColor *icc_color); virtual int render(FilterSlot &slot, FilterUnits const &units); virtual void area_enlarge(NRRectL &area, Geom::Matrix const &trans); private: double opacity; guint32 color; + SVGICCColor *icc; }; } /* namespace Filters */ diff --git a/src/display/nr-filter-image.cpp b/src/display/nr-filter-image.cpp index 2b799f8d2..4ad6982f3 100644 --- a/src/display/nr-filter-image.cpp +++ b/src/display/nr-filter-image.cpp @@ -2,7 +2,7 @@ * feImage filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Tavmjong Bah <tavmjong@free.fr> * * Copyright (C) 2007 authors diff --git a/src/display/nr-filter-morphology.cpp b/src/display/nr-filter-morphology.cpp index 2df3ff807..258298751 100644 --- a/src/display/nr-filter-morphology.cpp +++ b/src/display/nr-filter-morphology.cpp @@ -2,7 +2,7 @@ * feMorphology filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-morphology.h b/src/display/nr-filter-morphology.h index 1d3e16be3..16ccad5e6 100644 --- a/src/display/nr-filter-morphology.h +++ b/src/display/nr-filter-morphology.h @@ -5,7 +5,7 @@ * feMorphology filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-tile.cpp b/src/display/nr-filter-tile.cpp index 53399eba2..898db9f53 100644 --- a/src/display/nr-filter-tile.cpp +++ b/src/display/nr-filter-tile.cpp @@ -2,7 +2,7 @@ * feTile filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-tile.h b/src/display/nr-filter-tile.h index ea826dfd7..5a6a5a78c 100644 --- a/src/display/nr-filter-tile.h +++ b/src/display/nr-filter-tile.h @@ -5,7 +5,7 @@ * feTile filter primitive renderer * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2007 authors * diff --git a/src/display/nr-filter-turbulence.cpp b/src/display/nr-filter-turbulence.cpp index a91db3d56..8d22b180d 100644 --- a/src/display/nr-filter-turbulence.cpp +++ b/src/display/nr-filter-turbulence.cpp @@ -3,7 +3,7 @@ * * Authors: * World Wide Web Consortium <http://www.w3.org/> - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * This file has a considerable amount of code adapted from * the W3C SVG filter specs, available at: diff --git a/src/display/nr-filter-turbulence.h b/src/display/nr-filter-turbulence.h index b12e6395a..b841cc37f 100644 --- a/src/display/nr-filter-turbulence.h +++ b/src/display/nr-filter-turbulence.h @@ -6,7 +6,7 @@ * * Authors: * World Wide Web Consortium <http://www.w3.org/> - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Niko Kiirala <niko@kiirala.com> * * This file has a considerable amount of code adapted from diff --git a/src/display/nr-svgfonts.cpp b/src/display/nr-svgfonts.cpp index 2d71504d2..7a0db664a 100644 --- a/src/display/nr-svgfonts.cpp +++ b/src/display/nr-svgfonts.cpp @@ -4,7 +4,7 @@ * SVGFonts rendering implementation * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/display/nr-svgfonts.h b/src/display/nr-svgfonts.h index ebf5ad08b..ddf4ba327 100644 --- a/src/display/nr-svgfonts.h +++ b/src/display/nr-svgfonts.h @@ -5,7 +5,7 @@ * SVGFonts rendering headear * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/display/rendermode.h b/src/display/rendermode.h index 1b59ae9bb..abcdb3db4 100644 --- a/src/display/rendermode.h +++ b/src/display/rendermode.h @@ -12,7 +12,8 @@ namespace Inkscape { enum RenderMode { RENDERMODE_NORMAL, RENDERMODE_NO_FILTERS, - RENDERMODE_OUTLINE + RENDERMODE_OUTLINE, + RENDERMODE_PRINT_COLORS_PREVIEW }; } diff --git a/src/display/snap-indicator.cpp b/src/display/snap-indicator.cpp index 20ea7d58c..84bc1709b 100644 --- a/src/display/snap-indicator.cpp +++ b/src/display/snap-indicator.cpp @@ -40,17 +40,16 @@ SnapIndicator::~SnapIndicator() } void -SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const p) +SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p) { remove_snaptarget(); //only display one snaptarget at a time g_assert(_desktop != NULL); - /* Commented out for now, because this might hide any snapping bug! if (!p.getSnapped()) { - return; // If we haven't snapped, then it is of no use to draw a snapindicator + g_warning("No snapping took place, so no snap target will be displayed"); + return; // If we haven't snapped, then it is of no use to draw a snapindicator } - */ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool value = prefs->getBool("/options/snapindicator/value", true); @@ -98,9 +97,6 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const p) case SNAPTARGET_BBOX_EDGE: target_name = _("bounding box side"); break; - case SNAPTARGET_GRADIENTS_PARENT_BBOX: - target_name = _("bounding box"); - break; case SNAPTARGET_PAGE_BORDER: target_name = _("page border"); break; @@ -140,6 +136,9 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const p) case SNAPTARGET_TEXT_BASELINE: target_name = _("text baseline"); break; + case SNAPTARGET_CONSTRAINED_ANGLE: + target_name = _("constrained angle"); + break; default: g_warning("Snap target has not yet been defined!"); break; @@ -265,7 +264,7 @@ SnapIndicator::remove_snaptarget() } void -SnapIndicator::set_new_snapsource(std::pair<Geom::Point, int> const p) +SnapIndicator::set_new_snapsource(Inkscape::SnapCandidatePoint const &p) { remove_snapsource(); @@ -285,7 +284,7 @@ SnapIndicator::set_new_snapsource(std::pair<Geom::Point, int> const p) "shape", SP_KNOT_SHAPE_CIRCLE, NULL ); - SP_CTRL(canvasitem)->moveto(p.first); + SP_CTRL(canvasitem)->moveto(p.getPoint()); _snapsource = _desktop->add_temporary_canvasitem(canvasitem, 1000); } } diff --git a/src/display/snap-indicator.h b/src/display/snap-indicator.h index 4391ca6d6..d896042a2 100644 --- a/src/display/snap-indicator.h +++ b/src/display/snap-indicator.h @@ -26,10 +26,10 @@ public: SnapIndicator(SPDesktop *desktop); virtual ~SnapIndicator(); - void set_new_snaptarget(Inkscape::SnappedPoint const p); + void set_new_snaptarget(Inkscape::SnappedPoint const &p); void remove_snaptarget(); - void set_new_snapsource(std::pair<Geom::Point, int> const p); + void set_new_snapsource(Inkscape::SnapCandidatePoint const &p); void remove_snapsource(); protected: diff --git a/src/document.cpp b/src/document.cpp index d406f3712..101c54e30 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -63,10 +63,16 @@ #include "xml/repr.h" #include "xml/rebase-hrefs.h" -#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) +// Higher number means lower priority. +#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 2) + +// Should have a lower priority than SP_DOCUMENT_UPDATE_PRIORITY, +// since we want it to happen when there are no more updates. +#define SP_DOCUMENT_REROUTING_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) static gint sp_document_idle_handler(gpointer data); +static gint sp_document_rerouting_handler(gpointer data); gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer data); @@ -88,15 +94,16 @@ SPDocument::SPDocument() : priv(0), // reset in ctor actionkey(0), modified_id(0), + rerouting_handler_id(0), profileManager(0), // deferred until after other initialization - router(new Avoid::Router()), - perspectives(0), - current_persp3d(0), + router(new Avoid::Router(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)), _collection_queue(0), - oldSignalsConnected(false) + oldSignalsConnected(false), + current_persp3d(0) { - // Don't use the Consolidate moves optimisation. - router->ConsolidateMoves = false; + // Penalise libavoid for choosing paths with needless extra segments. + // This results in much better looking orthogonal connector paths. + router->setRoutingPenalty(Avoid::segmentPenalty); SPDocumentPrivate *p = new SPDocumentPrivate(); @@ -132,6 +139,11 @@ SPDocument::~SPDocument() { profileManager = 0; } + if (router) { + delete router; + router = NULL; + } + if (priv) { if (priv->partial) { sp_repr_free_log(priv->partial); @@ -177,10 +189,15 @@ SPDocument::~SPDocument() { } if (modified_id) { - gtk_idle_remove(modified_id); + g_source_remove(modified_id); modified_id = 0; } + if (rerouting_handler_id) { + g_source_remove(rerouting_handler_id); + rerouting_handler_id = 0; + } + if (oldSignalsConnected) { g_signal_handlers_disconnect_by_func(G_OBJECT(INKSCAPE), reinterpret_cast<gpointer>(sp_document_reset_key), @@ -195,35 +212,46 @@ SPDocument::~SPDocument() { keepalive = FALSE; } - if (router) { - delete router; - router = NULL; + //delete this->_whiteboard_session_manager; +} + +Persp3D * +SPDocument::getCurrentPersp3D() { + // Check if current_persp3d is still valid + std::vector<Persp3D*> plist; + getPerspectivesInDefs(plist); + for (unsigned int i = 0; i < plist.size(); ++i) { + if (current_persp3d == plist[i]) + return current_persp3d; } - //delete this->_whiteboard_session_manager; + // If not, return the first perspective in defs (which may be NULL of none exists) + current_persp3d = persp3d_document_first_persp (this); + return current_persp3d; } -void SPDocument::add_persp3d (Persp3D * const /*persp*/) -{ - SPDefs *defs = SP_ROOT(this->root)->defs; - for (SPObject *i = sp_object_first_child(SP_OBJECT(defs)); i != NULL; i = SP_OBJECT_NEXT(i) ) { - if (SP_IS_PERSP3D(i)) { - g_print ("Encountered a Persp3D in defs\n"); - } - } +Persp3DImpl * +SPDocument::getCurrentPersp3DImpl() { + return current_persp3d_impl; +} - g_print ("Adding Persp3D to defs\n"); - persp3d_create_xml_element (this); +void +SPDocument::setCurrentPersp3D(Persp3D * const persp) { + current_persp3d = persp; + //current_persp3d_impl = persp->perspective_impl; } -void SPDocument::remove_persp3d (Persp3D * const /*persp*/) -{ - // TODO: Delete the repr, maybe perform a check if any boxes are still linked to the perspective. - // Anything else? - g_print ("Please implement deletion of perspectives here.\n"); +void +SPDocument::getPerspectivesInDefs(std::vector<Persp3D*> &list) { + SPDefs *defs = SP_ROOT(this->root)->defs; + for (SPObject *i = sp_object_first_child(SP_OBJECT(defs)); i != NULL; i = SP_OBJECT_NEXT(i) ) { + if (SP_IS_PERSP3D(i)) + list.push_back(SP_PERSP3D(i)); + } } +/** void SPDocument::initialize_current_persp3d() { this->current_persp3d = persp3d_document_first_persp(this); @@ -231,6 +259,7 @@ void SPDocument::initialize_current_persp3d() this->current_persp3d = persp3d_create_xml_element(this); } } +**/ unsigned long SPDocument::serial() const { return priv->serial; @@ -378,10 +407,14 @@ sp_document_create(Inkscape::XML::Document *rdoc, inkscape_ref(); } - // Remark: Here, we used to create a "currentpersp3d" element in the document defs. - // But this is probably a bad idea since we need to adapt it for every change of selection, which will - // completely clutter the undo history. Maybe rather save it to prefs on exit and re-read it on startup? - document->initialize_current_persp3d(); + // Check if the document already has a perspective (e.g., when opening an existing + // document). If not, create a new one and set it as the current perspective. + document->setCurrentPersp3D(persp3d_document_first_persp(document)); + if (!document->getCurrentPersp3D()) { + //document->setCurrentPersp3D(persp3d_create_xml_element (document)); + Persp3DImpl *persp_impl = new Persp3DImpl(); + document->setCurrentPersp3DImpl(persp_impl); + } sp_document_set_undo_sensitive(document, true); @@ -582,26 +615,101 @@ Geom::Point sp_document_dimensions(SPDocument *doc) } /** + * Gets page fitting margin information from the namedview node in the XML. + * \param nv_repr reference to this document's namedview + * \param key the same key used by the RegisteredScalarUnit in + * ui/widget/page-sizer.cpp + * \param margin_units units for the margin + * \param return_units units to return the result in + * \param width width in px (for percentage margins) + * \param height height in px (for percentage margins) + * \param use_width true if the this key is left or right margins, false + * otherwise. Used for percentage margins. + * \return the margin size in px, else 0.0 if anything is invalid. + */ +static double getMarginLength(Inkscape::XML::Node * const nv_repr, + gchar const * const key, + SPUnit const * const margin_units, + SPUnit const * const return_units, + double const width, + double const height, + bool const use_width) +{ + double value; + if (!sp_repr_get_double (nv_repr, key, &value)) { + return 0.0; + } + if (margin_units == &sp_unit_get_by_id (SP_UNIT_PERCENT)) { + return (use_width)? width * value : height * value; + } + if (!sp_convert_distance (&value, margin_units, return_units)) { + return 0.0; + } + return value; +} + +/** * Given a Geom::Rect that may, for example, correspond to the bbox of an object, * this function fits the canvas to that rect by resizing the canvas * and translating the document root into position. + * \param rect fit document size to this + * \param with_margins add margins to rect, by taking margins from this + * document's namedview (<sodipodi:namedview> "fit-margin-..." + * attributes, and "units") */ -void SPDocument::fitToRect(Geom::Rect const &rect) +void SPDocument::fitToRect(Geom::Rect const &rect, bool with_margins) { double const w = rect.width(); double const h = rect.height(); double const old_height = sp_document_height(this); SPUnit const &px(sp_unit_get_by_id(SP_UNIT_PX)); - sp_document_set_width(this, w, &px); - sp_document_set_height(this, h, &px); + + /* in px */ + double margin_top = 0.0; + double margin_left = 0.0; + double margin_right = 0.0; + double margin_bottom = 0.0; + + SPNamedView *nv = sp_document_namedview(this, 0); + + if (with_margins && nv) { + Inkscape::XML::Node *nv_repr = SP_OBJECT_REPR (nv); + if (nv_repr != NULL) { + gchar const * const units_abbr = nv_repr->attribute("units"); + SPUnit const *margin_units = NULL; + if (units_abbr != NULL) { + margin_units = sp_unit_get_by_abbreviation(units_abbr); + } + if (margin_units == NULL) { + margin_units = &sp_unit_get_by_id(SP_UNIT_PX); + } + margin_top = getMarginLength(nv_repr, "fit-margin-top", + margin_units, &px, w, h, false); + margin_left = getMarginLength(nv_repr, "fit-margin-left", + margin_units, &px, w, h, true); + margin_right = getMarginLength(nv_repr, "fit-margin-right", + margin_units, &px, w, h, true); + margin_bottom = getMarginLength(nv_repr, "fit-margin-bottom", + margin_units, &px, w, h, false); + } + } + + Geom::Rect const rect_with_margins( + rect.min() - Geom::Point(margin_left, margin_bottom), + rect.max() + Geom::Point(margin_right, margin_top)); + + + sp_document_set_width(this, rect_with_margins.width(), &px); + sp_document_set_height(this, rect_with_margins.height(), &px); - Geom::Translate const tr(Geom::Point(0, (old_height - h)) - - to_2geom(rect.min())); + Geom::Translate const tr( + Geom::Point(0, old_height - rect_with_margins.height()) + - to_2geom(rect_with_margins.min())); SP_GROUP(root)->translateChildItems(tr); - SPNamedView *nv = sp_document_namedview(this, 0); + if(nv) { - Geom::Translate tr2(-rect.min()); + Geom::Translate tr2(-rect_with_margins.min()); nv->translateGuides(tr2); // update the viewport so the drawing appears to stay where it was @@ -734,11 +842,13 @@ SPDocument::emitReconstructionFinish(void) { // printf("Finishing Reconstruction\n"); priv->_reconstruction_finish_signal.emit(); - + +/** // Reference to the old persp3d object is invalid after reconstruction. initialize_current_persp3d(); return; +**/ } sigc::connection SPDocument::connectCommit(SPDocument::CommitSignal::slot_type slot) @@ -852,7 +962,12 @@ void sp_document_request_modified(SPDocument *doc) { if (!doc->modified_id) { - doc->modified_id = gtk_idle_add_priority(SP_DOCUMENT_UPDATE_PRIORITY, sp_document_idle_handler, doc); + doc->modified_id = g_idle_add_full(SP_DOCUMENT_UPDATE_PRIORITY, + sp_document_idle_handler, doc, NULL); + } + if (!doc->rerouting_handler_id) { + doc->rerouting_handler_id = g_idle_add_full(SP_DOCUMENT_REROUTING_PRIORITY, + sp_document_rerouting_handler, doc, NULL); } } @@ -908,26 +1023,49 @@ SPDocument::_updateDocument() * Repeatedly works on getting the document updated, since sometimes * it takes more than one pass to get the document updated. But it * usually should not take more than a few loops, and certainly never - * more than 64 iterations. So we bail out if we hit 64 iterations, + * more than 32 iterations. So we bail out if we hit 32 iterations, * since this typically indicates we're stuck in an update loop. */ gint sp_document_ensure_up_to_date(SPDocument *doc) { - int counter = 64; - while (!doc->_updateDocument()) { - if (counter == 0) { - g_warning("More than 64 iteration while updating document '%s'", doc->uri? doc->uri:"<unknown URI, probably clipboard>"); + // Bring the document up-to-date, specifically via the following: + // 1a) Process all document updates. + // 1b) When completed, process connector routing changes. + // 2a) Process any updates resulting from connector reroutings. + int counter = 32; + for (unsigned int pass = 1; pass <= 2; ++pass) { + // Process document updates. + while (!doc->_updateDocument()) { + if (counter == 0) { + g_warning("More than 32 iteration while updating document '%s'", doc->uri); + break; + } + counter--; + } + if (counter == 0) + { break; } - counter--; - } + // After updates on the first pass we get libavoid to process all the + // changed objects and provide new routings. This may cause some objects + // to be modified, hence the second update pass. + if (pass == 1) { + doc->router->processTransaction(); + } + } + if (doc->modified_id) { /* Remove handler */ - gtk_idle_remove(doc->modified_id); + g_source_remove(doc->modified_id); doc->modified_id = 0; } + if (doc->rerouting_handler_id) { + /* Remove handler */ + g_source_remove(doc->rerouting_handler_id); + doc->rerouting_handler_id = 0; + } return counter>0; } @@ -947,6 +1085,24 @@ sp_document_idle_handler(gpointer data) } } +/** + * An idle handler to reroute connectors in the document. + */ +static gint +sp_document_rerouting_handler(gpointer data) +{ + // Process any queued movement actions and determine new routings for + // object-avoiding connectors. Callbacks will be used to update and + // redraw affected connectors. + SPDocument *doc = static_cast<SPDocument *>(data); + doc->router->processTransaction(); + + // We don't need to handle rerouting again until there are further + // diagram updates. + doc->rerouting_handler_id = 0; + return false; +} + static bool is_within(Geom::Rect const &area, Geom::Rect const &box) { return area.contains(box); diff --git a/src/document.h b/src/document.h index 789e3e2ed..e70582006 100644 --- a/src/document.h +++ b/src/document.h @@ -55,6 +55,7 @@ namespace Inkscape { class SP3DBox; class Persp3D; +class Persp3DImpl; namespace Proj { class TransfMat3x4; @@ -98,23 +99,35 @@ struct SPDocument : public Inkscape::GC::Managed<>, const gchar *actionkey; /// Handler ID guint modified_id; + + /// Connector rerouting handler ID + guint rerouting_handler_id; Inkscape::ProfileManager* profileManager; // Instance of the connector router Avoid::Router *router; - GSList *perspectives; - - Persp3D *current_persp3d; // "currently active" perspective (e.g., newly created boxes are attached to this one) - GSList *_collection_queue; bool oldSignalsConnected; - void add_persp3d(Persp3D * const persp); - void remove_persp3d(Persp3D * const persp); - void initialize_current_persp3d(); + void setCurrentPersp3D(Persp3D * const persp); + inline void setCurrentPersp3DImpl(Persp3DImpl * const persp_impl) { current_persp3d_impl = persp_impl; } + /* + * getCurrentPersp3D returns current_persp3d (if non-NULL) or the first + * perspective in the defs. If no perspective exists, returns NULL. + */ + Persp3D * getCurrentPersp3D(); + Persp3DImpl * getCurrentPersp3DImpl(); + void getPerspectivesInDefs(std::vector<Persp3D*> &list); + unsigned int numPerspectivesInDefs() { + std::vector<Persp3D*> list; + getPerspectivesInDefs(list); + return list.size(); + } + + //void initialize_current_persp3d(); sigc::connection connectModified(ModifiedSignal::slot_type slot); sigc::connection connectURISet(URISetSignal::slot_type slot); @@ -152,6 +165,9 @@ private: SPDocument(SPDocument const &); // no copy void operator=(SPDocument const &); // no assign + Persp3D *current_persp3d; /**< Currently 'active' perspective (to which, e.g., newly created boxes are attached) */ + Persp3DImpl *current_persp3d_impl; + public: sigc::connection connectReconstructionStart(ReconstructionStart::slot_type slot); sigc::connection connectReconstructionFinish(ReconstructionFinish::slot_type slot); @@ -163,7 +179,7 @@ public: sigc::connection _selection_changed_connection; sigc::connection _desktop_activated_connection; - void fitToRect(Geom::Rect const &rect); + void fitToRect(Geom::Rect const &rect, bool with_margins = false); }; SPDocument *sp_document_new(const gchar *uri, unsigned int keepalive, bool make_new = false); diff --git a/src/dom/ucd.cpp b/src/dom/ucd.cpp index 3747334d2..f9c36ad19 100644 --- a/src/dom/ucd.cpp +++ b/src/dom/ucd.cpp @@ -2514,11 +2514,12 @@ int uni_to_title(int ch) int uni_block(int ch) { - int ret; - UcdBlockData *entry; - for (entry = ucd_blocks, ret=0 ; entry->name ; entry++, ret++) - if (ch >= entry->low && ch <= entry->high) + int ret = 0; + for (UcdBlockData *entry = ucd_blocks; entry->name ; entry++, ret++) { + if ((ch >= entry->low) && (ch <= entry->high)) { return ret; + } + } return UCD_BLOCK_NO_BLOCK; } diff --git a/src/dom/util/ziptool.cpp b/src/dom/util/ziptool.cpp index 40f456bd6..1e915ab0a 100644 --- a/src/dom/util/ziptool.cpp +++ b/src/dom/util/ziptool.cpp @@ -2207,6 +2207,7 @@ ZipEntry *ZipFile::addFile(const std::string &fileName, ZipEntry *ze = new ZipEntry(); if (!ze->readFile(fileName, comment)) { + delete ze; return NULL; } entries.push_back(ze); diff --git a/src/draw-context.cpp b/src/draw-context.cpp index de9a7c7e5..3334c82de 100644 --- a/src/draw-context.cpp +++ b/src/draw-context.cpp @@ -511,9 +511,7 @@ void spdc_endpoint_snap_rotation(SPEventContext const *const ec, Geom::Point &p, /* Snap it along best vector */ SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager; m.setup(SP_EVENT_CONTEXT_DESKTOP(ec)); - Geom::Point pt2g = to_2geom(p); - m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best), false); - p = from_2geom(pt2g); + m.constrainedSnapReturnByRef( Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE, Inkscape::Snapper::ConstraintLine(best)); } } } diff --git a/src/dropper-context.cpp b/src/dropper-context.cpp index aa17ea859..5415fdc80 100644 --- a/src/dropper-context.cpp +++ b/src/dropper-context.cpp @@ -143,6 +143,16 @@ static void sp_dropper_context_finish(SPEventContext *ec) /** + * Returns the current dropper context icc-color. + */ +SPColor* sp_dropper_context_get_icc_color(SPEventContext */*ec*/) +{ + //TODO: implement-me! + + return 0; // At least we will cause a clean crash, instead of random corruption. +} + +/** * Returns the current dropper context color. */ guint32 sp_dropper_context_get_color(SPEventContext *ec) diff --git a/src/event-context.cpp b/src/event-context.cpp index 13e7e9410..363f9fe71 100644 --- a/src/event-context.cpp +++ b/src/event-context.cpp @@ -174,7 +174,7 @@ sp_event_context_dispose(GObject *object) } if (ec->_delayed_snap_event) { - delete ec->_delayed_snap_event; + delete ec->_delayed_snap_event; } G_OBJECT_CLASS(parent_class)->dispose(object); @@ -374,9 +374,9 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, switch (event->button.button) { case 1: if (event_context->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(event_context); - panning = 1; + // 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(event_context); + 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); @@ -387,9 +387,9 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, 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(event_context); - panning = 2; + // 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(event_context); + 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); @@ -399,9 +399,9 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, 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(event_context); - panning = 3; + // 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(event_context); + 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); @@ -421,7 +421,7 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK)) ) { /* Gdk seems to lose button release for us sometimes :-( */ - panning = 0; + panning = 0; sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); ret = TRUE; @@ -514,11 +514,11 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, } 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); + { + 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)) { + 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. @@ -545,11 +545,11 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, break; case GDK_Q: case GDK_q: - if (desktop->quick_zoomed()) { - ret = TRUE; - } + if (desktop->quick_zoomed()) { + ret = TRUE; + } if (!MOD__SHIFT && !MOD__CTRL && !MOD__ALT) { - desktop->zoom_quick(true); + desktop->zoom_quick(true); ret = TRUE; } break; @@ -632,7 +632,7 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, default: break; } - } + } break; case GDK_KEY_RELEASE: switch (get_group0_keyval(&event->key)) { @@ -651,8 +651,8 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, break; case GDK_Q: case GDK_q: - if (desktop->quick_zoomed()) { - desktop->zoom_quick(false); + if (desktop->quick_zoomed()) { + desktop->zoom_quick(false); ret = TRUE; } break; @@ -907,24 +907,24 @@ gint sp_event_context_root_handler(SPEventContext * 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->_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_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->_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; + // 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; + break; } return sp_event_context_virtual_root_handler(event_context, event); @@ -933,9 +933,9 @@ sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event) gint sp_event_context_virtual_root_handler(SPEventContext * event_context, GdkEvent * event) { - gint ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event); - set_event_location(event_context->desktop, event); - return ret; + gint ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event); + set_event_location(event_context->desktop, event); + return ret; } /** @@ -944,27 +944,27 @@ sp_event_context_virtual_root_handler(SPEventContext * event_context, GdkEvent * gint sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event) { - switch (event->type) { - case GDK_MOTION_NOTIFY: - sp_event_context_snap_delay_handler(event_context, item, NULL, (GdkEventMotion *)event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER); - break; - case GDK_BUTTON_RELEASE: - if (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; - } + switch (event->type) { + case GDK_MOTION_NOTIFY: + sp_event_context_snap_delay_handler(event_context, item, NULL, (GdkEventMotion *)event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER); + break; + case GDK_BUTTON_RELEASE: + if (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); } @@ -972,15 +972,15 @@ sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, Gdk gint sp_event_context_virtual_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event) { - gint ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->item_handler(event_context, item, event); + gint ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->item_handler(event_context, item, event); - if (! ret) { - ret = sp_event_context_virtual_root_handler(event_context, event); - } else { - set_event_location(event_context->desktop, event); - } + if (! ret) { + ret = sp_event_context_virtual_root_handler(event_context, event); + } else { + set_event_location(event_context->desktop, event); + } - return ret; + return ret; } /** @@ -1174,130 +1174,131 @@ event_context_print_event_info(GdkEvent *event, bool print_return) { void sp_event_context_snap_delay_handler(SPEventContext *ec, SPItem* const item, SPKnot* const knot, GdkEventMotion *event, DelayedSnapEvent::DelayedSnapEventOrigin origin) { - static guint32 prev_time; - static boost::optional<Geom::Point> prev_pos; + static guint32 prev_time; + static boost::optional<Geom::Point> prev_pos; - // Snapping occurs when dragging with the left mouse button down, or when hovering e.g. in the pen tool with left mouse button up + // 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 + // 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); + // 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, item, knot, 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 by this - ec->_delayed_snap_event = new DelayedSnapEvent(ec, item, knot, 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, item, knot, event, origin); - } - - prev_pos = event_pos; - prev_time = event_t; + // 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, item, knot, 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, item, knot, 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, item, knot, event, origin); + } + + prev_pos = event_pos; + prev_time = event_t; } } gboolean sp_event_context_snap_watchdog_callback(gpointer data) { - if (!data) return FALSE; - // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated - DelayedSnapEvent *dse = reinterpret_cast<DelayedSnapEvent*>(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; - } - - SPEventContext *ec = dse->getEventContext(); - if (ec == NULL || ec->desktop == NULL) { - return false; - } - - SPDesktop *dt = ec->desktop; - dt->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); - - switch (dse->getOrigin()) { - case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER: - sp_event_context_virtual_root_handler(ec, dse->getEvent()); - break; - case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: - { - SPItem* item = NULL; - item = dse->getItem(); - if (item && SP_IS_ITEM(item)) { - sp_event_context_virtual_item_handler(ec, item, dse->getEvent()); - } - } - break; - case DelayedSnapEvent::KNOT_HANDLER: - { - SPKnot* knot = dse->getKnot(); - if (knot && SP_IS_KNOT(knot)) { - sp_knot_handler_request_position(dse->getEvent(), knot); - } - } - break; - case DelayedSnapEvent::CONTROL_POINT_HANDLER: { + // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated + DelayedSnapEvent *dse = reinterpret_cast<DelayedSnapEvent*>(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; + } + + SPEventContext *ec = dse->getEventContext(); + if (ec == NULL || ec->desktop == NULL) { + return false; + } + + SPDesktop *dt = ec->desktop; + dt->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); + + switch (dse->getOrigin()) { + case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER: + sp_event_context_virtual_root_handler(ec, dse->getEvent()); + break; + case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: + { + SPItem* item = NULL; + item = dse->getItem(); + if (item && SP_IS_ITEM(item)) { + sp_event_context_virtual_item_handler(ec, item, dse->getEvent()); + } + } + break; + case DelayedSnapEvent::KNOT_HANDLER: + { + SPKnot* knot = dse->getKnot(); + if (knot && SP_IS_KNOT(knot)) { + sp_knot_handler_request_position(dse->getEvent(), knot); + } + } + break; + case DelayedSnapEvent::CONTROL_POINT_HANDLER: + { using Inkscape::UI::ControlPoint; ControlPoint *point = reinterpret_cast<ControlPoint*>(dse->getKnot()); point->_eventHandler(dse->getEvent()); - } break; - default: - g_warning("Origin of snap-delay event has not been defined!;"); - break; - } + } + break; + default: + g_warning("Origin of snap-delay event has not been defined!;"); + break; + } - ec->_delayed_snap_event = NULL; - delete dse; + ec->_delayed_snap_event = NULL; + delete dse; - return FALSE; //Kills the timer and stops it from executing this callback over and over again. + return FALSE; //Kills the timer and stops it from executing this callback over and over again. } void sp_event_context_discard_delayed_snap_event(SPEventContext *ec) { - delete ec->_delayed_snap_event; - ec->_delayed_snap_event = NULL; + delete ec->_delayed_snap_event; + ec->_delayed_snap_event = NULL; } diff --git a/src/extension/dxf2svg/entities2elements.cpp b/src/extension/dxf2svg/entities2elements.cpp index 15d3e76ae..f280bb2a8 100644 --- a/src/extension/dxf2svg/entities2elements.cpp +++ b/src/extension/dxf2svg/entities2elements.cpp @@ -82,7 +82,7 @@ void pline2svg(polyline pline, int type, int precision, char * units, double sca // 2 is pline2polygon - char delim[1]; + char delim[2]; double mag_bulge = 0; double prev_mag_bulge = 0; @@ -187,7 +187,7 @@ void lwpline2svg(lwpolyline pline, int type, int precision, char * units, double // 2 is pline2polygon - char delim[1]; + char delim[2]; double mag_bulge = 0; double prev_mag_bulge = 0; diff --git a/src/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp index 9ac19326f..737bb2885 100644 --- a/src/extension/internal/cairo-ps-out.cpp +++ b/src/extension/internal/cairo-ps-out.cpp @@ -42,22 +42,22 @@ namespace Inkscape { namespace Extension { namespace Internal { -bool -CairoPsOutput::check (Inkscape::Extension::Extension * module) +bool CairoPsOutput::check (Inkscape::Extension::Extension * /*module*/) { - if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS)) - return FALSE; - - return TRUE; + if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS)) { + return FALSE; + } else { + return TRUE; + } } -bool -CairoEpsOutput::check (Inkscape::Extension::Extension * module) +bool CairoEpsOutput::check (Inkscape::Extension::Extension * /*module*/) { - if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS)) - return FALSE; - - return TRUE; + if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS)) { + return FALSE; + } else { + return TRUE; + } } static bool diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h index e6f2d698e..a1f902457 100644 --- a/src/extension/internal/cairo-render-context.h +++ b/src/extension/internal/cairo-render-context.h @@ -71,12 +71,12 @@ public: CairoRenderer *getRenderer(void) const; cairo_t *getCairoContext(void) const; - typedef enum CairoRenderMode { + enum CairoRenderMode { RENDER_MODE_NORMAL, RENDER_MODE_CLIP }; - typedef enum CairoClipMode { + enum CairoClipMode { CLIP_MODE_PATH, CLIP_MODE_MASK }; diff --git a/src/extension/internal/emf-win32-inout.cpp b/src/extension/internal/emf-win32-inout.cpp index f400a3649..9d25f3a7f 100644 --- a/src/extension/internal/emf-win32-inout.cpp +++ b/src/extension/internal/emf-win32-inout.cpp @@ -127,6 +127,7 @@ emf_print_document_to_file(SPDocument *doc, gchar const *filename) /* Print document */ ret = mod->begin(doc); if (ret) { + g_free(oldoutput); throw Inkscape::Extension::Output::save_failed(); } sp_item_invoke_print(mod->base, &context); diff --git a/src/extension/internal/odf.cpp b/src/extension/internal/odf.cpp index cc8489302..46e0361ce 100644 --- a/src/extension/internal/odf.cpp +++ b/src/extension/internal/odf.cpp @@ -795,8 +795,8 @@ void SingularValueDecomposition::calculate() } } - delete e; - delete work; + delete [] e; + delete [] work; } diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp index c2d417f2c..ba00fe343 100644 --- a/src/extension/internal/pdfinput/pdf-input.cpp +++ b/src/extension/internal/pdfinput/pdf-input.cpp @@ -734,6 +734,7 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { delete builder; g_free(docname); delete pdf_doc; + delete dlg; // Restore undo sp_document_set_undo_sensitive(doc, saved); diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp index 00bd8fa4d..b9583545f 100644 --- a/src/extension/internal/pdfinput/svg-builder.cpp +++ b/src/extension/internal/pdfinput/svg-builder.cpp @@ -1536,7 +1536,7 @@ Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height } png_write_row(png_ptr, (png_bytep)buffer); } - delete buffer; + delete [] buffer; } else if (color_map) { image_stream = new ImageStream(str, width, color_map->getNumPixelComps(), @@ -1575,7 +1575,7 @@ Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height png_write_row(png_ptr, (png_bytep)buffer); } } - delete buffer; + delete [] buffer; } else { // A colormap must be provided, so quit png_destroy_write_struct(&png_ptr, &info_ptr); diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp index a3589e905..b10aa87ec 100644 --- a/src/extension/internal/svg.cpp +++ b/src/extension/internal/svg.cpp @@ -21,6 +21,7 @@ #include "extension/system.h" #include "extension/output.h" #include <vector> +#include "xml/attribute-record.h" #ifdef WITH_GNOME_VFS # include <libgnomevfs/gnome-vfs.h> @@ -32,6 +33,37 @@ namespace Internal { #include "clear-n_.h" + +using Inkscape::Util::List; +using Inkscape::XML::AttributeRecord; +using Inkscape::XML::Node; + + + +void pruneExtendedAttributes( Inkscape::XML::Node *repr ) +{ + if (repr) { + if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) { + std::vector<gchar const*> toBeRemoved; + for ( List<AttributeRecord const> it = repr->attributeList(); it; ++it ) { + const gchar* attrName = g_quark_to_string(it->key); + if ((strncmp("inkscape:", attrName, 9) == 0) || (strncmp("sodipodi:", attrName, 9) == 0)) { + toBeRemoved.push_back(attrName); + } + } + // Can't change the set we're interating over while we are iterating. + for ( std::vector<gchar const*>::iterator it = toBeRemoved.begin(); it != toBeRemoved.end(); ++it ) { + repr->setAttribute(*it, 0); + } + } + + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + pruneExtendedAttributes(child); + } + } +} + + /** \return None \brief What would an SVG editor be without loading/saving SVG @@ -179,7 +211,7 @@ Svg::open (Inkscape::Extension::Input */*mod*/, const gchar *uri) we're getting good data. It also checks the module ID of the incoming module to figure out whether this save should include the Inkscape namespace stuff or not. The result of that comparison - is stored in the spns variable. + is stored in the exportExtensions variable. If there is not to be Inkscape name spaces a new document is created without. (I think, I'm not sure on this code) @@ -198,18 +230,20 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena gchar *save_path = g_path_get_dirname(filename); - bool const spns = ( !mod->get_id() + bool const exportExtensions = ( !mod->get_id() || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE) || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE)); Inkscape::XML::Document *rdoc = NULL; Inkscape::XML::Node *repr = NULL; - if (spns) { + if (exportExtensions) { repr = sp_document_repr_root (doc); } else { rdoc = sp_repr_document_new ("svg:svg"); repr = rdoc->root(); repr = sp_document_root (doc)->updateRepr(rdoc, repr, SP_OBJECT_WRITE_BUILD); + + pruneExtendedAttributes(repr); } if (!sp_repr_save_rebased_file(repr->document(), filename, SP_SVG_NS_URI, @@ -217,7 +251,7 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena throw Inkscape::Extension::Output::save_failed(); } - if (!spns) { + if (!exportExtensions) { Inkscape::GC::release(rdoc); } diff --git a/src/filters/colormatrix.cpp b/src/filters/colormatrix.cpp index 55cfcbeb7..3f60ea05c 100644 --- a/src/filters/colormatrix.cpp +++ b/src/filters/colormatrix.cpp @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Sanches <felipe.sanches@gmail.com> + * Felipe Sanches <juca@members.fsf.org> * hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2007 Felipe C. da S. Sanches diff --git a/src/filters/componenttransfer-funcnode.cpp b/src/filters/componenttransfer-funcnode.cpp index e66f85e70..8edb9cf2d 100644 --- a/src/filters/componenttransfer-funcnode.cpp +++ b/src/filters/componenttransfer-funcnode.cpp @@ -7,7 +7,7 @@ * Authors: * Hugo Rodrigues <haa.rodrigues@gmail.com> * Niko Kiirala <niko@kiirala.com> - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2006, 2007, 2008 Authors * diff --git a/src/filters/componenttransfer-funcnode.h b/src/filters/componenttransfer-funcnode.h index 4db6ab785..52873f6d3 100644 --- a/src/filters/componenttransfer-funcnode.h +++ b/src/filters/componenttransfer-funcnode.h @@ -8,7 +8,7 @@ * Authors: * Hugo Rodrigues <haa.rodrigues@gmail.com> * Niko Kiirala <niko@kiirala.com> - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2006,2007 Authors * diff --git a/src/filters/convolvematrix.cpp b/src/filters/convolvematrix.cpp index 3e1c36f0a..6440f340a 100644 --- a/src/filters/convolvematrix.cpp +++ b/src/filters/convolvematrix.cpp @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/filters/convolvematrix.h b/src/filters/convolvematrix.h index beb5fad75..1e8545040 100644 --- a/src/filters/convolvematrix.h +++ b/src/filters/convolvematrix.h @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/filters/flood.cpp b/src/filters/flood.cpp index 625e35d42..221b0daf2 100644 --- a/src/filters/flood.cpp +++ b/src/filters/flood.cpp @@ -17,12 +17,13 @@ # include "config.h" #endif +#include "strneq.h" + #include "attributes.h" #include "svg/svg.h" #include "flood.h" #include "xml/repr.h" #include "helper-fns.h" -#include "svg/svg-color.h" /* FeFlood base class */ @@ -79,6 +80,7 @@ static void sp_feFlood_init(SPFeFlood *feFlood) { feFlood->opacity = 1; + feFlood->icc = NULL; } /** @@ -120,16 +122,34 @@ sp_feFlood_set(SPObject *object, unsigned int key, gchar const *value) gchar *end_ptr = NULL; guint32 read_color; double read_num; + bool dirty = false; switch(key) { /*DEAL WITH SETTING ATTRIBUTES HERE*/ case SP_PROP_FLOOD_COLOR: cend_ptr = NULL; read_color = sp_svg_read_color(value, &cend_ptr, 0xffffffff); + if (cend_ptr && read_color != feFlood->color){ feFlood->color = read_color; - object->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + dirty=true; + } + + if (cend_ptr){ + while (g_ascii_isspace(*cend_ptr)) { + ++cend_ptr; + } + if (strneq(cend_ptr, "icc-color(", 10)) { + if (!feFlood->icc) feFlood->icc = new SVGICCColor(); + if ( ! sp_svg_read_icc_color( cend_ptr, feFlood->icc ) ) { + delete feFlood->icc; + feFlood->icc = NULL; + } + dirty = true; + } } + if (dirty) + object->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_PROP_FLOOD_OPACITY: if (value) { @@ -208,6 +228,7 @@ static void sp_feFlood_build_renderer(SPFilterPrimitive *primitive, Inkscape::Fi nr_flood->set_opacity(sp_flood->opacity); nr_flood->set_color(sp_flood->color); + nr_flood->set_icc(sp_flood->icc); } diff --git a/src/filters/flood.h b/src/filters/flood.h index 046c0e868..f386e2cd4 100644 --- a/src/filters/flood.h +++ b/src/filters/flood.h @@ -15,6 +15,7 @@ #include "sp-filter.h" #include "flood-fns.h" +#include "svg/svg-icc-color.h" #include "display/nr-filter.h" #include "display/nr-filter-flood.h" @@ -25,6 +26,7 @@ class SPFeFloodClass; struct SPFeFlood : public SPFilterPrimitive { /** FLOOD ATTRIBUTES HERE */ guint32 color; + SVGICCColor *icc; double opacity; }; diff --git a/src/filters/image.cpp b/src/filters/image.cpp index d8e5a417f..eb6dfc22a 100644 --- a/src/filters/image.cpp +++ b/src/filters/image.cpp @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2007 Felipe Sanches @@ -43,8 +43,7 @@ static void sp_feImage_build_renderer(SPFilterPrimitive *primitive, Inkscape::Fi static SPFilterPrimitiveClass *feImage_parent_class; -GType -sp_feImage_get_type() +GType sp_feImage_get_type() { static GType feImage_type = 0; @@ -64,8 +63,7 @@ sp_feImage_get_type() return feImage_type; } -static void -sp_feImage_class_init(SPFeImageClass *klass) +static void sp_feImage_class_init(SPFeImageClass *klass) { SPObjectClass *sp_object_class = (SPObjectClass *)klass; SPFilterPrimitiveClass * sp_primitive_class = (SPFilterPrimitiveClass *)klass; @@ -81,8 +79,7 @@ sp_feImage_class_init(SPFeImageClass *klass) sp_primitive_class->build_renderer = sp_feImage_build_renderer; } -static void -sp_feImage_init(SPFeImage */*feImage*/) +static void sp_feImage_init(SPFeImage */*feImage*/) { } @@ -91,8 +88,7 @@ sp_feImage_init(SPFeImage */*feImage*/) * our name must be associated with a repr via "sp_object_type_register". Best done through * sp-object-repr.cpp's repr_name_entries array. */ -static void -sp_feImage_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +static void sp_feImage_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) { // Save document reference so we can load images with relative paths. SPFeImage *feImage = SP_FEIMAGE(object); @@ -115,8 +111,7 @@ sp_feImage_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *re /** * Drops any allocated memory. */ -static void -sp_feImage_release(SPObject *object) +static void sp_feImage_release(SPObject *object) { SPFeImage *feImage = SP_FEIMAGE(object); feImage->_image_modified_connection.disconnect(); @@ -127,14 +122,12 @@ sp_feImage_release(SPObject *object) ((SPObjectClass *) feImage_parent_class)->release(object); } -static void -sp_feImage_elem_modified(SPObject* /*href*/, guint /*flags*/, SPObject* obj) +static void sp_feImage_elem_modified(SPObject* /*href*/, guint /*flags*/, SPObject* obj) { obj->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); } -static void -sp_feImage_href_modified(SPObject* old_elem, SPObject* new_elem, SPObject* obj) +static void sp_feImage_href_modified(SPObject* /*old_elem*/, SPObject* new_elem, SPObject* obj) { SPFeImage *feImage = SP_FEIMAGE(obj); feImage->_image_modified_connection.disconnect(); @@ -151,8 +144,7 @@ sp_feImage_href_modified(SPObject* old_elem, SPObject* new_elem, SPObject* obj) /** * Sets a specific value in the SPFeImage. */ -static void -sp_feImage_set(SPObject *object, unsigned int key, gchar const *value) +static void sp_feImage_set(SPObject *object, unsigned int key, gchar const *value) { SPFeImage *feImage = SP_FEIMAGE(object); (void)feImage; @@ -221,8 +213,7 @@ sp_feImage_set(SPObject *object, unsigned int key, gchar const *value) /** * Receives update notifications. */ -static void -sp_feImage_update(SPObject *object, SPCtx *ctx, guint flags) +static void sp_feImage_update(SPObject *object, SPCtx *ctx, guint flags) { if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | @@ -239,8 +230,7 @@ sp_feImage_update(SPObject *object, SPCtx *ctx, guint flags) /** * Writes its settings to an incoming repr object, if any. */ -static Inkscape::XML::Node * -sp_feImage_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) +static Inkscape::XML::Node * sp_feImage_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { /* TODO: Don't just clone, but create a new repr node and write all * relevant values into it */ diff --git a/src/filters/image.h b/src/filters/image.h index 7207918e1..78e719ac7 100644 --- a/src/filters/image.h +++ b/src/filters/image.h @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/filters/morphology.cpp b/src/filters/morphology.cpp index 9a34bbccb..1530dae8c 100644 --- a/src/filters/morphology.cpp +++ b/src/filters/morphology.cpp @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Sanches <felipe.sanches@gmail.com> + * Felipe Sanches <juca@members.fsf.org> * Hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/filters/turbulence.cpp b/src/filters/turbulence.cpp index f3c143056..eed056ecc 100644 --- a/src/filters/turbulence.cpp +++ b/src/filters/turbulence.cpp @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2007 Felipe Sanches diff --git a/src/filters/turbulence.h b/src/filters/turbulence.h index 5edf678a7..792a6181a 100644 --- a/src/filters/turbulence.h +++ b/src/filters/turbulence.h @@ -6,7 +6,7 @@ */ /* * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * Hugo Rodrigues <haa.rodrigues@gmail.com> * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index c16ed2456..726f4d78a 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -594,49 +594,19 @@ gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gp } } - if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) { - // Try snapping to the grid or guides - m.setup(desktop); - Inkscape::SnappedPoint s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p), Inkscape::SNAPSOURCE_HANDLE); + m.setup(desktop); + if (!((state & GDK_SHIFT_MASK) || (state & GDK_CONTROL_MASK))) { + Inkscape::SnappedPoint s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_OTHER, Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_HANDLE)); if (s.getSnapped()) { p = s.getPoint(); sp_knot_moveto (knot, p); - } else if (m.snapprefs.getSnapEnabledGlobally() && m.snapprefs.getSnapModeNode() && !(m.snapprefs.getSnapPostponedGlobally())) { - bool was_snapped = false; - double dist = NR_HUGE; - // No snapping so far, let's see if we need to snap to any of the levels - for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) { - dist = fabs(p[Geom::Y] - dragger->parent->hor_levels[i]); - if (dist < snap_dist) { - p[Geom::Y] = dragger->parent->hor_levels[i]; - s = Inkscape::SnappedPoint(p, Inkscape::SNAPSOURCE_HANDLE, Inkscape::SNAPTARGET_GRADIENTS_PARENT_BBOX, dist, snap_dist, false, false); - was_snapped = true; - sp_knot_moveto (knot, p); - } - } - for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) { - dist = fabs(p[Geom::X] - dragger->parent->vert_levels[i]); - if (dist < snap_dist) { - p[Geom::X] = dragger->parent->vert_levels[i]; - s = Inkscape::SnappedPoint(p, Inkscape::SNAPSOURCE_HANDLE, Inkscape::SNAPTARGET_GRADIENTS_PARENT_BBOX, dist, snap_dist, false, false); - was_snapped = true; - sp_knot_moveto (knot, p); - } - } - if (was_snapped) { - desktop->snapindicator->set_new_snaptarget(s); - } } - } - - if (state & GDK_CONTROL_MASK) { + } else if (state & GDK_CONTROL_MASK) { + SnappedConstraints sc; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); /* 0 means no snapping. */ - // This list will store snap vectors from all draggables of dragger - GSList *snap_vectors = NULL; - for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { GrDraggable *draggable = (GrDraggable *) i->data; @@ -687,25 +657,27 @@ gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gp // with Ctrl, snap to M_PI/snaps snap_vector = get_snap_vector (p, dr_snap, M_PI/snaps, 0); } - } - if (snap_vector) { - snap_vectors = g_slist_prepend (snap_vectors, &(*snap_vector)); + if (snap_vector) { + Inkscape::Snapper::ConstraintLine cl(dr_snap, p + *snap_vector - dr_snap); + Inkscape::SnappedPoint s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_OTHER, Inkscape::SnapCandidatePoint(p + *snap_vector, Inkscape::SNAPSOURCE_HANDLE), cl); + if (s.getSnapped()) { + s.setTransformation(s.getPoint() - p); + sc.points.push_back(s); + } else { + Inkscape::SnappedPoint dummy(p + *snap_vector, Inkscape::SNAPSOURCE_HANDLE, 0, Inkscape::SNAPTARGET_CONSTRAINED_ANGLE, Geom::L2(*snap_vector), 10000, true, false); + dummy.setTransformation(*snap_vector); + sc.points.push_back(dummy); + } + } } } - // Move by the smallest of snap vectors: - Geom::Point move(9999, 9999); - for (GSList const *i = snap_vectors; i != NULL; i = i->next) { - Geom::Point *snap_vector = (Geom::Point *) i->data; - if (Geom::L2(*snap_vector) < Geom::L2(move)) - move = *snap_vector; - } - if (move[Geom::X] < 9999) { - p += move; + Inkscape::SnappedPoint bsp = m.findBestSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_HANDLE), sc, true); // snap indicator will be displayed if needed + + if (bsp.getSnapped()) { + p += bsp.getTransformation(); sp_knot_moveto (knot, p); } - - g_slist_free(snap_vectors); } drag->keep_selection = (bool) g_list_find(drag->selected, dragger); diff --git a/src/graphlayout/graphlayout.cpp b/src/graphlayout/graphlayout.cpp index cd1683c34..81ea59059 100644 --- a/src/graphlayout/graphlayout.cpp +++ b/src/graphlayout/graphlayout.cpp @@ -31,6 +31,7 @@ #include "style.h" #include "conn-avoid-ref.h" #include "libavoid/connector.h" +#include "libavoid/router.h" #include "libavoid/geomtypes.h" #include "libcola/cola.h" #include "libvpsc/generate-constraints.h" diff --git a/src/guide-snapper.cpp b/src/guide-snapper.cpp index 5cf97958a..9121e3ee2 100644 --- a/src/guide-snapper.cpp +++ b/src/guide-snapper.cpp @@ -68,22 +68,22 @@ bool Inkscape::GuideSnapper::ThisSnapperMightSnap() const return (_snap_enabled && _snapmanager->snapprefs.getSnapToGuides() && _snapmanager->getNamedView()->showguides); } -void Inkscape::GuideSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, Geom::Point const point_on_line) const +void Inkscape::GuideSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, Geom::Point const point_on_line) const { - SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, Inkscape::SNAPTARGET_GUIDE, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); + SnappedLine dummy = SnappedLine(snapped_point, snapped_distance, source, source_num, Inkscape::SNAPTARGET_GUIDE, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); sc.guide_lines.push_back(dummy); } -void Inkscape::GuideSnapper::_addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source) const +void Inkscape::GuideSnapper::_addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const { - SnappedPoint dummy = SnappedPoint(origin, source, Inkscape::SNAPTARGET_GUIDE_ORIGIN, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + SnappedPoint dummy = SnappedPoint(origin, source, source_num, Inkscape::SNAPTARGET_GUIDE_ORIGIN, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); sc.points.push_back(dummy); } -void Inkscape::GuideSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const +void Inkscape::GuideSnapper::_addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const { - SnappedPoint dummy = SnappedPoint(snapped_point, source, Inkscape::SNAPTARGET_GUIDE, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + SnappedPoint dummy = SnappedPoint(snapped_point, source, source_num, Inkscape::SNAPTARGET_GUIDE, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); sc.points.push_back(dummy); } diff --git a/src/guide-snapper.h b/src/guide-snapper.h index 1dc602f72..5adac6e22 100644 --- a/src/guide-snapper.h +++ b/src/guide-snapper.h @@ -30,13 +30,13 @@ public: bool ThisSnapperMightSnap() const; Geom::Coord getSnapperTolerance() const; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom) - bool getSnapperAlwaysSnap() const; //if true, then the snapper will always snap, regardless of its tolerance + bool getSnapperAlwaysSnap() const; //if true, then the snapper will always snap, regardless of its tolerance private: LineList _getSnapLines(Geom::Point const &p) const; - void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, Geom::Point const point_on_line) const; - void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source) const; - void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const; + void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, Geom::Point const point_on_line) const; + void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const; + void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const; }; } diff --git a/src/helper-fns.h b/src/helper-fns.h index d2a6c9b22..2c2db92c4 100644 --- a/src/helper-fns.h +++ b/src/helper-fns.h @@ -5,7 +5,7 @@ * Some helper functions * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * * Copyright (C) 2006 Hugo Rodrigues diff --git a/src/inkjar/jar.cpp b/src/inkjar/jar.cpp index d53901de5..6897cb317 100644 --- a/src/inkjar/jar.cpp +++ b/src/inkjar/jar.cpp @@ -66,7 +66,7 @@ namespace Inkjar { JarFile::JarFile(gchar const*new_filename) { - _filename = strdup(new_filename); + _filename = g_strdup(new_filename); _last_filename = NULL; fd = -1; } @@ -75,7 +75,7 @@ JarFile::JarFile(gchar const*new_filename) // use strdup gchar *JarFile::get_last_filename() const { - return (_last_filename != NULL ? strdup(_last_filename) : NULL); + return (_last_filename != NULL ? g_strdup(_last_filename) : NULL); } JarFile::~JarFile() @@ -445,11 +445,11 @@ JarFile& JarFile::operator=(JarFile const& rhs) if (_filename == NULL) _filename = NULL; else - _filename = strdup(rhs._filename); + _filename = g_strdup(rhs._filename); if (_last_filename == NULL) _last_filename = NULL; else - _last_filename = strdup(rhs._last_filename); + _last_filename = g_strdup(rhs._last_filename); fd = rhs.fd; return *this; diff --git a/src/inkscape.rc b/src/inkscape.rc index d48b68c43..9163658f0 100644 --- a/src/inkscape.rc +++ b/src/inkscape.rc @@ -3,8 +3,8 @@ APPLICATION_ICON ICON DISCARDABLE "../inkscape.ico" 1 24 DISCARDABLE "./inkscape-manifest.xml" 1 VERSIONINFO - FILEVERSION 0,46,0,0 - PRODUCTVERSION 0,46,0,0 + FILEVERSION 0,47,0,9 + PRODUCTVERSION 0,47,0,9 BEGIN BLOCK "StringFileInfo" BEGIN @@ -13,11 +13,11 @@ BEGIN VALUE "Comments", "Published under the GNU GPL" VALUE "CompanyName", "inkscape.org" VALUE "FileDescription", "Inkscape" - VALUE "FileVersion", "0.46.0" + VALUE "FileVersion", "0.47+devel" VALUE "InternalName", "Inkscape" - VALUE "LegalCopyright", "© 2007 Inkscape" + VALUE "LegalCopyright", "© 2009 Inkscape" VALUE "ProductName", "Inkscape" - VALUE "ProductVersion", "0.46.0" + VALUE "ProductVersion", "0.47+devel" END END BLOCK "VarFileInfo" diff --git a/src/inkview.rc b/src/inkview.rc index 9f643eb4c..390f4fb07 100644 --- a/src/inkview.rc +++ b/src/inkview.rc @@ -3,8 +3,8 @@ APPLICATION_ICON ICON DISCARDABLE "../inkscape.ico" 1 24 DISCARDABLE "./inkview-manifest.xml"
1 VERSIONINFO
- FILEVERSION 0,46,0,0
- PRODUCTVERSION 0,46,0,0
+ FILEVERSION 0,47,0,9
+ PRODUCTVERSION 0,47,0,9
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -13,11 +13,11 @@ BEGIN VALUE "Comments", "Published under the GNU GPL"
VALUE "CompanyName", "inkscape.org"
VALUE "FileDescription", "Inkview"
- VALUE "FileVersion", "0.46.0"
+ VALUE "FileVersion", "0.47+devel"
VALUE "InternalName", "Inkview"
- VALUE "LegalCopyright", "© 2007 Inkscape"
+ VALUE "LegalCopyright", "© 2009 Inkscape"
VALUE "ProductName", "Inkview"
- VALUE "ProductVersion", "0.46.0"
+ VALUE "ProductVersion", "0.47+devel"
END
END
BLOCK "VarFileInfo"
diff --git a/src/interface.cpp b/src/interface.cpp index cf7072064..b29b91d18 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -703,6 +703,8 @@ update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_dat new_state = mode == Inkscape::RENDERMODE_NO_FILTERS; } else if (!strcmp(action->id, "ViewModeOutline")) { new_state = mode == Inkscape::RENDERMODE_OUTLINE; + } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) { + new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW; } else { g_warning("update_view_menu does not handle this verb"); } @@ -992,6 +994,9 @@ sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI: gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname()); gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter); + gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE); + gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE); + GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent")); gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu); diff --git a/src/libavoid/CMakeLists.txt b/src/libavoid/CMakeLists.txt index df532c564..3f408074c 100644 --- a/src/libavoid/CMakeLists.txt +++ b/src/libavoid/CMakeLists.txt @@ -11,6 +11,8 @@ static.cpp timer.cpp vertices.cpp visibility.cpp +orthogonal.cpp +vpsc.cpp ) ADD_LIBRARY(avoid STATIC ${libavoid_SRC}) TARGET_LINK_LIBRARIES(avoid diff --git a/src/libavoid/Makefile_insert b/src/libavoid/Makefile_insert index f75470e26..77728499c 100644 --- a/src/libavoid/Makefile_insert +++ b/src/libavoid/Makefile_insert @@ -11,24 +11,26 @@ libavoid_libavoid_a_SOURCES = \ libavoid/debug.h \ libavoid/geometry.cpp \ libavoid/geometry.h \ + libavoid/geomtypes.cpp \ libavoid/geomtypes.h \ libavoid/graph.cpp \ libavoid/graph.h \ libavoid/makepath.cpp \ libavoid/makepath.h \ - libavoid/polyutil.cpp \ - libavoid/polyutil.h \ + libavoid/orthogonal.cpp \ + libavoid/orthogonal.h \ + libavoid/vpsc.cpp \ + libavoid/vpsc.h \ libavoid/router.cpp \ libavoid/router.h \ libavoid/shape.cpp \ libavoid/shape.h \ - libavoid/static.cpp \ - libavoid/static.h \ libavoid/timer.cpp \ libavoid/timer.h \ libavoid/vertices.cpp \ libavoid/vertices.h \ libavoid/visibility.cpp \ libavoid/visibility.h \ - libavoid/libavoid.h \ - libavoid/region.h + libavoid/viscluster.cpp \ + libavoid/viscluster.h \ + libavoid/libavoid.h diff --git a/src/libavoid/assertions.h b/src/libavoid/assertions.h new file mode 100644 index 000000000..0725c4482 --- /dev/null +++ b/src/libavoid/assertions.h @@ -0,0 +1,49 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + +#ifndef AVOID_ASSERTIONS_H +#define AVOID_ASSERTIONS_H + +#ifdef NDEBUG + + #define COLA_ASSERT(expr) static_cast<void>(0) + +#else // Not NDEBUG + + #if defined(USE_ASSERT_EXCEPTIONS) + + #include "libvpsc/assertions.h" + + #else + + #include <cassert> + #define COLA_ASSERT(expr) assert(expr) + + #endif + +#endif + + +#endif // AVOID_ASSERTIONS_H + diff --git a/src/libavoid/connector.cpp b/src/libavoid/connector.cpp index 647303371..3dbd941a4 100644 --- a/src/libavoid/connector.cpp +++ b/src/libavoid/connector.cpp @@ -2,95 +2,236 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + +#include <cstring> +#include <cfloat> +#include <cmath> #include <cstdlib> + #include "libavoid/graph.h" #include "libavoid/connector.h" #include "libavoid/makepath.h" #include "libavoid/visibility.h" #include "libavoid/debug.h" #include "libavoid/router.h" +#include "libavoid/assertions.h" namespace Avoid { + +ConnEnd::ConnEnd(const Point& point) + : _point(point), + _directions(ConnDirAll), + _shapeRef(NULL) +{ +} + + +ConnEnd::ConnEnd(const Point& point, const ConnDirFlags visDirs) + : _point(point), + _directions(visDirs), + _shapeRef(NULL) +{ +} + +ConnEnd::ConnEnd(ShapeRef *shapeRef, const double x_pos, const double y_pos, + const double insideOffset, const ConnDirFlags visDirs) + : _directions(visDirs), + _shapeRef(shapeRef), + _xPosition(x_pos), + _yPosition(y_pos), + _insideOffset(insideOffset) +{ +} + +const Point ConnEnd::point(void) const +{ + if (_shapeRef) + { + const Polygon& poly = _shapeRef->polygon(); + + double x_min = DBL_MAX; + double x_max = -DBL_MAX; + double y_min = DBL_MAX; + double y_max = -DBL_MAX; + for (size_t i = 0; i < poly.size(); ++i) + { + x_min = std::min(x_min, poly.ps[i].x); + x_max = std::max(x_max, poly.ps[i].x); + y_min = std::min(y_min, poly.ps[i].y); + y_max = std::max(y_max, poly.ps[i].y); + } + + Point point; + + // We want to place connection points on the edges of shapes, + // or possibly slightly inside them (if _insideOfset is set). + + point.vn = kUnassignedVertexNumber; + if (_xPosition == ATTACH_POS_LEFT) + { + point.x = x_min + _insideOffset; + point.vn = 6; + } + else if (_xPosition == ATTACH_POS_RIGHT) + { + point.x = x_max - _insideOffset; + point.vn = 4; + } + else + { + point.x = x_min + (_xPosition * (x_max - x_min)); + } + + if (_yPosition == ATTACH_POS_TOP) + { + point.y = y_max - _insideOffset; + point.vn = 5; + } + else if (_yPosition == ATTACH_POS_BOTTOM) + { + point.y = y_min + _insideOffset; + point.vn = 7; + } + else + { + point.y = y_min + (_yPosition * (y_max - y_min)); + point.vn = kUnassignedVertexNumber; + } + + return point; + } + else + { + return _point; + } +} + + +ConnDirFlags ConnEnd::directions(void) const +{ + if (_shapeRef) + { + ConnDirFlags visDir = _directions; + if (_directions == ConnDirNone) + { + // None is set, use the defaults: + if (_xPosition == ATTACH_POS_LEFT) + { + visDir = ConnDirLeft; + } + else if (_xPosition == ATTACH_POS_RIGHT) + { + visDir = ConnDirRight; + } + if (_yPosition == ATTACH_POS_TOP) + { + visDir = ConnDirDown; + } + else if (_yPosition == ATTACH_POS_BOTTOM) + { + visDir = ConnDirUp; + } + + if (visDir == ConnDirNone) + { + visDir = ConnDirAll; + } + } + return visDir; + } + else + { + return _directions; + } +} + + ConnRef::ConnRef(Router *router, const unsigned int id) - : _router(router) - , _id(id) - , _type(ConnType_PolyLine) - , _srcId(0) - , _dstId(0) - , _needs_reroute_flag(true) - , _false_path(false) - , _active(false) - , _route_dist(0) - , _srcVert(NULL) - , _dstVert(NULL) - , _initialised(false) - , _callback(NULL) - , _connector(NULL) - , _hateCrossings(false) + : _router(router), + _type(router->validConnType()), + _srcId(0), + _dstId(0), + _needs_reroute_flag(true), + _false_path(false), + _needs_repaint(false), + _active(false), + _route_dist(0), + _srcVert(NULL), + _dstVert(NULL), + _startVert(NULL), + _initialised(false), + _callback(NULL), + _connector(NULL), + _hateCrossings(false) { + _id = router->assignId(id); + // TODO: Store endpoints and details. - _route.pn = 0; - _route.ps = NULL; -} - - -ConnRef::ConnRef(Router *router, const unsigned int id, - const Point& src, const Point& dst) - : _router(router) - , _id(id) - , _type(ConnType_PolyLine) - , _srcId(0) - , _dstId(0) - , _needs_reroute_flag(true) - , _false_path(false) - , _active(false) - , _route_dist(0) - , _srcVert(NULL) - , _dstVert(NULL) - , _initialised(false) - , _callback(NULL) - , _connector(NULL) - , _hateCrossings(false) -{ - _route.pn = 0; - _route.ps = NULL; - - if (_router->IncludeEndpoints) - { - bool isShape = false; - _srcVert = new VertInf(_router, VertID(id, isShape, 1), src); - _dstVert = new VertInf(_router, VertID(id, isShape, 2), dst); - _router->vertices.addVertex(_srcVert); - _router->vertices.addVertex(_dstVert); - makeActive(); - _initialised = true; - } + _route.clear(); +} + + +ConnRef::ConnRef(Router *router, const ConnEnd& src, const ConnEnd& dst, + const unsigned int id) + : _router(router), + _type(router->validConnType()), + _srcId(0), + _dstId(0), + _needs_reroute_flag(true), + _false_path(false), + _needs_repaint(false), + _active(false), + _route_dist(0), + _srcVert(NULL), + _dstVert(NULL), + _initialised(false), + _callback(NULL), + _connector(NULL), + _hateCrossings(false) +{ + _id = router->assignId(id); + _route.clear(); + + bool isShape = false; + _srcVert = new VertInf(_router, VertID(_id, isShape, 1), src.point()); + _srcVert->visDirections = src.directions(); + _dstVert = new VertInf(_router, VertID(_id, isShape, 2), dst.point()); + _dstVert->visDirections = dst.directions(); + makeActive(); + _initialised = true; + + setEndpoints(src, dst); } ConnRef::~ConnRef() { - freeRoute(); + _router->removeQueuedConnectorActions(this); + removeFromGraph(); + + freeRoutes(); if (_srcVert) { @@ -106,27 +247,38 @@ ConnRef::~ConnRef() _dstVert = NULL; } - if (_active) - { - makeInactive(); - } + makeInactive(); } -void ConnRef::setType(unsigned int type) +ConnType ConnRef::routingType(void) const { - _type = type; + return _type; } -void ConnRef::updateEndPoint(const unsigned int type, const Point& point) +void ConnRef::setRoutingType(ConnType type) { - assert((type == (unsigned int) VertID::src) || - (type == (unsigned int) VertID::tar)); - - // XXX: This was commented out. Is there a case where it isn't true? - assert(_router->IncludeEndpoints); + type = _router->validConnType(type); + if (_type != type) + { + _type = type; + + makePathInvalid(); + + _router->modifyConnector(this); + } +} + +void ConnRef::common_updateEndPoint(const unsigned int type, const ConnEnd& connEnd) +{ + const Point& point = connEnd.point(); + //db_printf("common_updateEndPoint(%d,(pid=%d,vn=%d,(%f,%f)))\n", + // type,point.id,point.vn,point.x,point.y); + COLA_ASSERT((type == (unsigned int) VertID::src) || + (type == (unsigned int) VertID::tar)); + if (!_initialised) { makeActive(); @@ -141,28 +293,28 @@ void ConnRef::updateEndPoint(const unsigned int type, const Point& point) { if (_srcVert) { - _srcVert->Reset(point); + _srcVert->Reset(VertID(_id, isShape, type), point); } else { _srcVert = new VertInf(_router, VertID(_id, isShape, type), point); - _router->vertices.addVertex(_srcVert); } + _srcVert->visDirections = connEnd.directions(); altered = _srcVert; partner = _dstVert; } - else // if (type == (unsigned int) VertID::dst) + else // if (type == (unsigned int) VertID::tar) { if (_dstVert) { - _dstVert->Reset(point); + _dstVert->Reset(VertID(_id, isShape, type), point); } else { _dstVert = new VertInf(_router, VertID(_id, isShape, type), point); - _router->vertices.addVertex(_dstVert); } + _dstVert->visDirections = connEnd.directions(); altered = _dstVert; partner = _srcVert; @@ -171,8 +323,85 @@ void ConnRef::updateEndPoint(const unsigned int type, const Point& point) // XXX: Seems to be faster to just remove the edges and recreate bool isConn = true; altered->removeFromGraph(isConn); - bool knownNew = true; - vertexVisibility(altered, partner, knownNew, true); + + makePathInvalid(); + _router->setStaticGraphInvalidated(true); +} + + +void ConnRef::setEndpoints(const ConnEnd& srcPoint, const ConnEnd& dstPoint) +{ + _router->modifyConnector(this, VertID::src, srcPoint); + _router->modifyConnector(this, VertID::tar, dstPoint); +} + + +void ConnRef::setEndpoint(const unsigned int type, const ConnEnd& connEnd) +{ + _router->modifyConnector(this, type, connEnd); +} + + +void ConnRef::setSourceEndpoint(const ConnEnd& srcPoint) +{ + _router->modifyConnector(this, VertID::src, srcPoint); +} + + +void ConnRef::setDestEndpoint(const ConnEnd& dstPoint) +{ + _router->modifyConnector(this, VertID::tar, dstPoint); +} + + +void ConnRef::updateEndPoint(const unsigned int type, const ConnEnd& connEnd) +{ + common_updateEndPoint(type, connEnd); + + if (_router->_polyLineRouting) + { + bool knownNew = true; + bool genContains = true; + if (type == (unsigned int) VertID::src) + { + vertexVisibility(_srcVert, _dstVert, knownNew, genContains); + } + else + { + vertexVisibility(_dstVert, _srcVert, knownNew, genContains); + } + } +} + + +bool ConnRef::setEndpoint(const unsigned int type, const VertID& pointID, + Point *pointSuggestion) +{ + VertInf *vInf = _router->vertices.getVertexByID(pointID); + if (vInf == NULL) + { + return false; + } + Point& point = vInf->point; + if (pointSuggestion) + { + if (euclideanDist(point, *pointSuggestion) > 0.5) + { + return false; + } + } + + common_updateEndPoint(type, point); + + // Give this visibility just to the point it is over. + EdgeInf *edge = new EdgeInf( + (type == VertID::src) ? _srcVert : _dstVert, vInf); + // XXX: We should be able to set this to zero, but can't due to + // assumptions elsewhere in the code. + edge->setDist(0.001); + + _router->processTransaction(); + return true; } @@ -203,7 +432,7 @@ unsigned int ConnRef::getDstShapeId(void) void ConnRef::makeActive(void) { - assert(!_active); + COLA_ASSERT(!_active); // Add to connRefs list. _pos = _router->connRefs.insert(_router->connRefs.begin(), this); @@ -213,7 +442,7 @@ void ConnRef::makeActive(void) void ConnRef::makeInactive(void) { - assert(_active); + COLA_ASSERT(_active); // Remove from connRefs list. _router->connRefs.erase(_pos); @@ -221,54 +450,69 @@ void ConnRef::makeInactive(void) } -void ConnRef::freeRoute(void) +void ConnRef::freeRoutes(void) { - if (_route.ps) - { - _route.pn = 0; - std::free(_route.ps); - _route.ps = NULL; - } + _route.clear(); + _display_route.clear(); } -PolyLine& ConnRef::route(void) +const PolyLine& ConnRef::route(void) const { return _route; } -void ConnRef::calcRouteDist(void) +PolyLine& ConnRef::routeRef(void) { - _route_dist = 0; - for (int i = 1; i < _route.pn; i++) + return _route; +} + + +void ConnRef::set_route(const PolyLine& route) +{ + if (&_display_route == &route) { - _route_dist += dist(_route.ps[i], _route.ps[i - 1]); + db_printf("Error:\tTrying to update libavoid route with itself.\n"); + return; } + _display_route.ps = route.ps; + + //_display_route.clear(); } -bool ConnRef::needsReroute(void) +Polygon& ConnRef::displayRoute(void) { - return (_false_path || _needs_reroute_flag); + if (_display_route.empty()) + { + // No displayRoute is set. Simplify the current route to get it. + _display_route = _route.simplify(); + } + return _display_route; } -void ConnRef::lateSetup(const Point& src, const Point& dst) +void ConnRef::calcRouteDist(void) { - assert(!_initialised); + double (*dist)(const Point& a, const Point& b) = + (_type == ConnType_PolyLine) ? euclideanDist : manhattanDist; - bool isShape = false; - _srcVert = new VertInf(_router, VertID(_id, isShape, 1), src); - _dstVert = new VertInf(_router, VertID(_id, isShape, 2), dst); - _router->vertices.addVertex(_srcVert); - _router->vertices.addVertex(_dstVert); - makeActive(); - _initialised = true; + _route_dist = 0; + for (size_t i = 1; i < _route.size(); ++i) + { + _route_dist += dist(_route.at(i), _route.at(i - 1)); + } } -unsigned int ConnRef::id(void) +bool ConnRef::needsRepaint(void) const +{ + return _needs_repaint; +} + + +unsigned int ConnRef::id(void) const { return _id; } @@ -286,6 +530,12 @@ VertInf *ConnRef::dst(void) } +VertInf *ConnRef::start(void) +{ + return _startVert; +} + + bool ConnRef::isInitialised(void) { return _initialised; @@ -303,29 +553,8 @@ void ConnRef::unInitialise(void) void ConnRef::removeFromGraph(void) { - for (VertInf *iter = _srcVert; iter != NULL; ) - { - VertInf *tmp = iter; - iter = (iter == _srcVert) ? _dstVert : NULL; - - // For each vertex. - EdgeInfList& visList = tmp->visList; - EdgeInfList::iterator finish = visList.end(); - EdgeInfList::iterator edge; - while ((edge = visList.begin()) != finish) - { - // Remove each visibility edge - delete (*edge); - } - - EdgeInfList& invisList = tmp->invisList; - finish = invisList.end(); - while ((edge = invisList.begin()) != finish) - { - // Remove each invisibility edge - delete (*edge); - } - } + _srcVert->removeFromGraph(); + _dstVert->removeFromGraph(); } @@ -336,12 +565,11 @@ void ConnRef::setCallback(void (*cb)(void *), void *ptr) } -void ConnRef::handleInvalid(void) +void ConnRef::performCallback(void) { - if (_false_path || _needs_reroute_flag) { - if (_callback) { - _callback(_connector); - } + if (_callback) + { + _callback(_connector); } } @@ -352,79 +580,279 @@ void ConnRef::makePathInvalid(void) } -Router *ConnRef::router(void) +Router *ConnRef::router(void) const { return _router; } -int ConnRef::generatePath(Point p0, Point p1) +bool ConnRef::generatePath(Point /*p0*/, Point /*p1*/) +{ + // XXX Code to determine when connectors really need to be rerouted + // does not yet work for orthogonal connectors. + if (_type != ConnType_Orthogonal) + { + if (!_false_path && !_needs_reroute_flag) + { + // This connector is up to date. + return false; + } + } + + bool result = generatePath(); + + return result; +} + + +// Validates a bend point on a path to check it does not form a zigzag corner. +// a, b, c are consecutive points on the path. d and e are b's neighbours, +// forming the shape corner d-b-e. +// +bool validateBendPoint(VertInf *aInf, VertInf *bInf, VertInf *cInf) { - if (!_false_path && !_needs_reroute_flag) { + bool bendOkay = true; + + if ((aInf == NULL) || (cInf == NULL)) + { + // Not a bendpoint, i.e., the end of the connector, so don't test. + return bendOkay; + } + + COLA_ASSERT(bInf != NULL); + VertInf *dInf = bInf->shPrev; + VertInf *eInf = bInf->shNext; + COLA_ASSERT(dInf != NULL); + COLA_ASSERT(eInf != NULL); + + Point& a = aInf->point; + Point& b = bInf->point; + Point& c = cInf->point; + Point& d = dInf->point; + Point& e = eInf->point; + + if ((a == b) || (b == c)) + { + return bendOkay; + } + +#ifdef PATHDEBUG + db_printf("a=(%g, %g)\n", a.x, a.y); + db_printf("b=(%g, %g)\n", b.x, b.y); + db_printf("c=(%g, %g)\n", c.x, c.y); + db_printf("d=(%g, %g)\n", d.x, d.y); + db_printf("e=(%g, %g)\n", e.x, e.y); +#endif + // Check angle: + int abc = vecDir(a, b, c); +#ifdef PATHDEBUG + db_printf("(abc == %d) ", abc); +#endif + + if (abc == 0) + { + // The three consecutive point on the path are in a line. + // Thus, there should always be an equally short path that + // skips this bend point. + bendOkay = false; + } + else // (abc != 0) + { + COLA_ASSERT(vecDir(d, b, e) > 0); + int abe = vecDir(a, b, e); + int abd = vecDir(a, b, d); + int bce = vecDir(b, c, e); + int bcd = vecDir(b, c, d); +#ifdef PATHDEBUG + db_printf("&& (abe == %d) && (abd == %d) &&\n(bce == %d) && (bcd == %d)", + abe, abd, bce, bcd); +#endif + + bendOkay = false; + if (abe > 0) + { + if ((abc > 0) && (abd >= 0) && (bce >= 0)) + { + bendOkay = true; + } + } + else if (abd < 0) + { + if ((abc < 0) && (abe <= 0) && (bcd <= 0)) + { + bendOkay = true; + } + } + } +#ifdef PATHDEBUG + db_printf("\n"); +#endif + return bendOkay; +} + + +bool ConnRef::generatePath(void) +{ + if (!_false_path && !_needs_reroute_flag) + { // This connector is up to date. - return (int) false; + return false; } + if (!_dstVert || !_srcVert) + { + // Connector is not fully initialised.. + return false; + } + + //COLA_ASSERT(_srcVert->point != _dstVert->point); + _false_path = false; _needs_reroute_flag = false; - VertInf *src = _srcVert; VertInf *tar = _dstVert; + _startVert = _srcVert; - if ( !(_router->IncludeEndpoints) ) - { - lateSetup(p0, p1); - - // Update as they have just been set by lateSetup. - src = _srcVert; - tar = _dstVert; + bool *flag = &(_needs_reroute_flag); - bool knownNew = true; - bool genContains = true; - vertexVisibility(src, tar, knownNew, genContains); - vertexVisibility(tar, src, knownNew, genContains); + size_t existingPathStart = 0; + const PolyLine& currRoute = route(); + if (_router->RubberBandRouting) + { + COLA_ASSERT(_router->IgnoreRegions == true); + +#ifdef PATHDEBUG + db_printf("\n"); + _srcVert->id.db_print(); + db_printf(": %g, %g\n", _srcVert->point.x, _srcVert->point.y); + tar->id.db_print(); + db_printf(": %g, %g\n", tar->point.x, tar->point.y); + for (size_t i = 0; i < currRoute.ps.size(); ++i) + { + db_printf("%g, %g ", currRoute.ps[i].x, currRoute.ps[i].y); + } + db_printf("\n"); +#endif + if (currRoute.size() > 2) + { + if (_srcVert->point == currRoute.ps[0]) + { + existingPathStart = currRoute.size() - 2; + COLA_ASSERT(existingPathStart != 0); + const Point& pnt = currRoute.at(existingPathStart); + bool isShape = true; + VertID vID(pnt.id, isShape, pnt.vn); + + _startVert = _router->vertices.getVertexByID(vID); + } + } + } + //db_printf("GO\n"); + //db_printf("src: %X strt: %X dst: %x\n", (int) _srcVert, (int) _startVert, (int) _dstVert); + bool found = false; + while (!found) + { + makePath(this, flag); + for (VertInf *i = tar; i != NULL; i = i->pathNext) + { + if (i == _srcVert) + { + found = true; + break; + } + } + if (!found) + { + if (existingPathStart == 0) + { + break; + } +#ifdef PATHDEBUG + db_printf("BACK\n"); +#endif + existingPathStart--; + const Point& pnt = currRoute.at(existingPathStart); + bool isShape = (existingPathStart > 0); + VertID vID(pnt.id, isShape, pnt.vn); + + _startVert = _router->vertices.getVertexByID(vID); + COLA_ASSERT(_startVert); + } + else if (_router->RubberBandRouting) + { + // found. + bool unwind = false; + +#ifdef PATHDEBUG + db_printf("\n\n\nSTART:\n\n"); +#endif + VertInf *prior = NULL; + for (VertInf *curr = tar; curr != _startVert->pathNext; + curr = curr->pathNext) + { + if (!validateBendPoint(curr->pathNext, curr, prior)) + { + unwind = true; + break; + } + prior = curr; + } + if (unwind) + { +#ifdef PATHDEBUG + db_printf("BACK II\n"); +#endif + if (existingPathStart == 0) + { + break; + } + existingPathStart--; + const Point& pnt = currRoute.at(existingPathStart); + bool isShape = (existingPathStart > 0); + VertID vID(pnt.id, isShape, pnt.vn); + + _startVert = _router->vertices.getVertexByID(vID); + COLA_ASSERT(_startVert); + + found = false; + } + } } - bool *flag = &(_needs_reroute_flag); - - makePath(this, flag); bool result = true; int pathlen = 1; - for (VertInf *i = tar; i != src; i = i->pathNext) + for (VertInf *i = tar; i != _srcVert; i = i->pathNext) { pathlen++; if (i == NULL) { db_printf("Warning: Path not found...\n"); pathlen = 2; - tar->pathNext = src; - if (_router->InvisibilityGrph) + tar->pathNext = _srcVert; + if ((_type == ConnType_PolyLine) && _router->InvisibilityGrph) { // TODO: Could we know this edge already? - EdgeInf *edge = EdgeInf::existingEdge(src, tar); - assert(edge != NULL); + EdgeInf *edge = EdgeInf::existingEdge(_srcVert, tar); + COLA_ASSERT(edge != NULL); edge->addCycleBlocker(); } - result = false; break; } - if (pathlen > 100) - { - fprintf(stderr, "ERROR: Should never be here...\n"); - exit(1); - } + // Check we don't have an apparent infinite connector path. + COLA_ASSERT(pathlen < 200); } - Point *path = (Point *) malloc(pathlen * sizeof(Point)); + std::vector<Point> path(pathlen); int j = pathlen - 1; - for (VertInf *i = tar; i != src; i = i->pathNext) + for (VertInf *i = tar; i != _srcVert; i = i->pathNext) { - if (_router->InvisibilityGrph) + if (_router->InvisibilityGrph && (_type == ConnType_PolyLine)) { // TODO: Again, we could know this edge without searching. EdgeInf *edge = EdgeInf::existingEdge(i, i->pathNext); + COLA_ASSERT(edge != NULL); edge->addConn(flag); } else @@ -432,25 +860,53 @@ int ConnRef::generatePath(Point p0, Point p1) _false_path = true; } path[j] = i->point; - path[j].id = i->id.objID; + if (i->id.isShape) + { + path[j].id = i->id.objID; + path[j].vn = i->id.vn; + } + else + { + path[j].id = _id; + path[j].vn = kUnassignedVertexNumber; + } j--; - } - path[0] = src->point; + if (i->pathNext && (i->pathNext->point == i->point)) + { + if (i->pathNext->id.isShape && i->id.isShape) + { + // Check for consecutive points on opposite + // corners of two touching shapes. + COLA_ASSERT(abs(i->pathNext->id.objID - i->id.objID) != 2); + } + } + } + path[0] = _srcVert->point; + // Use topbit to differentiate between start and end point of connector. + // They need unique IDs for nudging. + unsigned int topbit = ((unsigned int) 1) << 31; + path[0].id = _id | topbit; + path[0].vn = kUnassignedVertexNumber; // Would clear visibility for endpoints here if required. - PolyLine& output_route = route(); - output_route.pn = pathlen; + freeRoutes(); + PolyLine& output_route = _route; output_route.ps = path; - if ( !(_router->IncludeEndpoints) ) +#ifdef PATHDEBUG + db_printf("Output route:\n"); + for (size_t i = 0; i < output_route.ps.size(); ++i) { - assert(_initialised); - unInitialise(); + db_printf("[%d,%d] %g, %g ", output_route.ps[i].id, + output_route.ps[i].vn, output_route.ps[i].x, + output_route.ps[i].y); } - - return (int) result; + db_printf("\n\n"); +#endif + + return result; } @@ -466,6 +922,895 @@ bool ConnRef::doesHateCrossings(void) } +PtOrder::~PtOrder() +{ + // Free the PointRep lists. + for (int dim = 0; dim < 2; ++dim) + { + PointRepList::iterator curr = connList[dim].begin(); + while (curr != connList[dim].end()) + { + PointRep *doomed = *curr; + curr = connList[dim].erase(curr); + delete doomed; + } + } +} + +bool PointRep::follow_inner(PointRep *target) +{ + if (this == target) + { + return true; + } + else + { + for (PointRepSet::iterator curr = inner_set.begin(); + curr != inner_set.end(); ++curr) + { + if ((*curr)->follow_inner(target)) + { + return true; + } + } + } + return false; +} + + +int PtOrder::positionFor(const ConnRef *conn, const size_t dim) const +{ + int position = 0; + for (PointRepList::const_iterator curr = connList[dim].begin(); + curr != connList[dim].end(); ++curr) + { + if ((*curr)->conn == conn) + { + return position; + } + ++position; + } + // Not found. + return -1; +} + + +bool PtOrder::addPoints(const int dim, PtConnPtrPair innerArg, + PtConnPtrPair outerArg, bool swapped) +{ + PtConnPtrPair inner = (swapped) ? outerArg : innerArg; + PtConnPtrPair outer = (swapped) ? innerArg : outerArg; + COLA_ASSERT(inner != outer); + + //printf("addPoints(%d, [%g, %g]-%X, [%g, %g]-%X)\n", dim, + // inner->x, inner->y, (int) inner, outer->x, outer->y, (int) outer); + + PointRep *innerPtr = NULL; + PointRep *outerPtr = NULL; + for (PointRepList::iterator curr = connList[dim].begin(); + curr != connList[dim].end(); ++curr) + { + if ((*curr)->point == inner.first) + { + innerPtr = *curr; + } + if ((*curr)->point == outer.first) + { + outerPtr = *curr; + } + } + + if (innerPtr == NULL) + { + innerPtr = new PointRep(inner.first, inner.second); + connList[dim].push_back(innerPtr); + } + + if (outerPtr == NULL) + { + outerPtr = new PointRep(outer.first, outer.second); + connList[dim].push_back(outerPtr); + } + // TODO COLA_ASSERT(innerPtr->inner_set.find(outerPtr) == innerPtr->inner_set.end()); + bool cycle = innerPtr->follow_inner(outerPtr); + if (cycle) + { + // Must reverse to avoid a cycle. + innerPtr->inner_set.insert(outerPtr); + } + else + { + outerPtr->inner_set.insert(innerPtr); + } + return cycle; +} + + +// Assuming that addPoints has been called for each pair of points in the +// shared path at that corner, then the contents of inner_set can be used +// to determine the correct ordering. +static bool pointRepLessThan(PointRep *r1, PointRep *r2) +{ + size_t r1less = r1->inner_set.size(); + size_t r2less = r2->inner_set.size(); + //COLA_ASSERT(r1less != r2less); + + return (r1less > r2less); +} + + +void PtOrder::sort(const int dim) +{ + connList[dim].sort(pointRepLessThan); +} + + +// Returns a vertex number representing a point on the line between +// two shape corners, represented by p0 and p1. +// +static int midVertexNumber(const Point& p0, const Point& p1, const Point& c) +{ + if (c.vn != kUnassignedVertexNumber) + { + // The split point is a shape corner, so doesn't need its + // vertex number adjusting. + return c.vn; + } + if ((p0.vn >= 4) && (p0.vn < kUnassignedVertexNumber)) + { + // The point next to this has the correct nudging direction, + // so use that. + return p0.vn; + } + if ((p1.vn >= 4) && (p1.vn < kUnassignedVertexNumber)) + { + // The point next to this has the correct nudging direction, + // so use that. + return p1.vn; + } + if ((p0.vn < 4) && (p1.vn < 4)) + { + if (p0.vn != p1.vn) + { + return p0.vn; + } + // Splitting between two ordinary shape corners. + int vn_mid = std::min(p0.vn, p1.vn); + if ((std::max(p0.vn, p1.vn) == 3) && (vn_mid == 0)) + { + vn_mid = 3; // Next vn is effectively 4. + } + return vn_mid + 4; + } + COLA_ASSERT((p0.x == p1.x) || (p0.y == p1.y)); + if (p0.vn != kUnassignedVertexNumber) + { + if (p0.x == p1.x) + { + if ((p0.vn == 2) || (p0.vn == 3)) + { + return 6; + } + return 4; + } + else + { + if ((p0.vn == 0) || (p0.vn == 3)) + { + return 7; + } + return 5; + } + } + else if (p1.vn != kUnassignedVertexNumber) + { + if (p0.x == p1.x) + { + if ((p1.vn == 2) || (p1.vn == 3)) + { + return 6; + } + return 4; + } + else + { + if ((p1.vn == 0) || (p1.vn == 3)) + { + return 7; + } + return 5; + } + } + + // Shouldn't both be new (kUnassignedVertexNumber) points. + db_printf("midVertexNumber(): p0.vn and p1.vn both = " + "kUnassignedVertexNumber\n"); + db_printf("p0.vn %d p1.vn %d\n", p0.vn, p1.vn); + return kUnassignedVertexNumber; +} + + +// Break up overlapping parallel segments that are not the same edge in +// the visibility graph, i.e., where one segment is a subsegment of another. +void splitBranchingSegments(Avoid::Polygon& poly, bool polyIsConn, + Avoid::Polygon& conn, const double tolerance) +{ + for (std::vector<Avoid::Point>::iterator i = conn.ps.begin(); + i != conn.ps.end(); ++i) + { + if (i == conn.ps.begin()) + { + // Skip the first point. + // There are points-1 segments in a connector. + continue; + } + + for (std::vector<Avoid::Point>::iterator j = poly.ps.begin(); + j != poly.ps.end(); ) + { + if (polyIsConn && (j == poly.ps.begin())) + { + // Skip the first point. + // There are points-1 segments in a connector. + ++j; + continue; + } + Point& c0 = *(i - 1); + Point& c1 = *i; + + Point& p0 = (j == poly.ps.begin()) ? poly.ps.back() : *(j - 1); + Point& p1 = *j; + + // Check the first point of the first segment. + if (((i - 1) == conn.ps.begin()) && + pointOnLine(p0, p1, c0, tolerance)) + { + //db_printf("add to poly %g %g\n", c0.x, c0.y); + + c0.vn = midVertexNumber(p0, p1, c0); + j = poly.ps.insert(j, c0); + if (j != poly.ps.begin()) + { + --j; + } + continue; + } + // And the second point of every segment. + if (pointOnLine(p0, p1, c1, tolerance)) + { + //db_printf("add to poly %g %g\n", c1.x, c1.y); + + c1.vn = midVertexNumber(p0, p1, c1); + j = poly.ps.insert(j, c1); + if (j != poly.ps.begin()) + { + --j; + } + continue; + } + + // Check the first point of the first segment. + if (polyIsConn && ((j - 1) == poly.ps.begin()) && + pointOnLine(c0, c1, p0, tolerance)) + { + //db_printf("add to conn %g %g\n", p0.x, p0.y); + + p0.vn = midVertexNumber(c0, c1, p0); + i = conn.ps.insert(i, p0); + continue; + } + // And the second point of every segment. + if (pointOnLine(c0, c1, p1, tolerance)) + { + //db_printf("add to conn %g %g\n", p1.x, p1.y); + + p1.vn = midVertexNumber(c0, c1, p1); + i = conn.ps.insert(i, p1); + } + ++j; + } + } +} + + +static int segDir(const Point& p1, const Point& p2) +{ + int result = 1; + if (p1.x == p2.x) + { + if (p2.y > p1.y) + { + result = -1; + } + } + else if (p1.y == p2.y) + { + if (p2.x < p1.x) + { + result = -1; + } + } + return result; +} + + +// Works out if the segment conn[cIndex-1]--conn[cIndex] really crosses poly. +// This does not not count non-crossing shared paths as crossings. +// poly can be either a connector (polyIsConn = true) or a cluster +// boundary (polyIsConn = false). +// +CrossingsInfoPair countRealCrossings(Avoid::Polygon& poly, + bool polyIsConn, Avoid::Polygon& conn, size_t cIndex, + bool checkForBranchingSegments, const bool finalSegment, + PointSet *crossingPoints, PtOrderMap *pointOrders, + ConnRef *polyConnRef, ConnRef *connConnRef) +{ + unsigned int crossingFlags = CROSSING_NONE; + if (checkForBranchingSegments) + { + size_t conn_pn = conn.size(); + // XXX When doing the pointOnLine test we allow the points to be + // slightly non-collinear. This addresses a problem with clustered + // routing where connectors could otherwise route cheaply through + // shape corners that were not quite on the cluster boundary, but + // reported to be on there by the line segment intersection code, + // which I suspect is not numerically accurate enough. This occured + // for points that only differed by about 10^-12 in the y-dimension. + double tolerance = (!polyIsConn) ? 0.00001 : 0.0; + splitBranchingSegments(poly, polyIsConn, conn, tolerance); + // cIndex is going to be the last, so take into account added points. + cIndex += (conn.size() - conn_pn); + } + COLA_ASSERT(cIndex >= 1); + COLA_ASSERT(cIndex < conn.size()); + + bool polyIsOrthogonal = (polyConnRef && + (polyConnRef->routingType() == ConnType_Orthogonal)); + bool connIsOrthogonal = (connConnRef && + (connConnRef->routingType() == ConnType_Orthogonal)); + + size_t poly_size = poly.size(); + int crossingCount = 0; + std::vector<Avoid::Point *> c_path; + std::vector<Avoid::Point *> p_path; + + Avoid::Point& a1 = conn.ps[cIndex - 1]; + Avoid::Point& a2 = conn.ps[cIndex]; + //db_printf("a1: %g %g\n", a1.x, a1.y); + //db_printf("a2: %g %g\n", a2.x, a2.y); + + for (size_t j = ((polyIsConn) ? 1 : 0); j < poly_size; ++j) + { + Avoid::Point& b1 = poly.ps[(j - 1 + poly_size) % poly_size]; + Avoid::Point& b2 = poly.ps[j]; + //db_printf("b1: %g %g\n", b1.x, b1.y); + //db_printf("b2: %g %g\n", b2.x, b2.y); + + p_path.clear(); + c_path.clear(); + bool converging = false; + + const bool a1_eq_b1 = (a1 == b1); + const bool a2_eq_b1 = (a2 == b1); + const bool a2_eq_b2 = (a2 == b2); + const bool a1_eq_b2 = (a1 == b2); + + if ( (a1_eq_b1 && a2_eq_b2) || + (a2_eq_b1 && a1_eq_b2) ) + { + if (finalSegment) + { + converging = true; + } + else + { + // Route along same segment: no penalty. We detect + // crossovers when we see the segments diverge. + continue; + } + } + else if (a2_eq_b1 || a2_eq_b2 || a1_eq_b2) + { + // Each crossing that is at a vertex in the + // visibility graph gets noticed four times. + // We ignore three of these cases. + // This also catches the case of a shared path, + // but this is one that terminates at a common + // endpoint, so we don't care about it. + continue; + } + + if (a1_eq_b1 || converging) + { + if (!converging) + { + if (polyIsConn && (j == 1)) + { + // Can't be the end of a shared path or crossing path + // since the common point is the first point of the + // connector path. This is not a shared path at all. + continue; + } + + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + // The segments share an endpoint -- a1==b1. + if (a2 == b0) + { + // a2 is not a split, continue. + continue; + } + } + + // If here and not converging, then we know that a2 != b2 + // And a2 and its pair in b are a split. + COLA_ASSERT(converging || !a2_eq_b2); + + bool shared_path = false; + + // Initial values here don't matter. They are only used after + // being set to sensible values, but we set them to stop a MSVC + // warning. + bool p_dir_back; + int p_dir = 0; + int trace_c = 0; + int trace_p = 0; + + if (converging) + { + // Determine direction we have to look through + // the points of connector b. + p_dir_back = a2_eq_b2 ? true : false; + p_dir = p_dir_back ? -1 : 1; + trace_c = (int) cIndex; + trace_p = (int) j; + if (!p_dir_back) + { + if (finalSegment) + { + trace_p--; + } + else + { + trace_c--; + } + } + + shared_path = true; + } + else if (cIndex >= 2) + { + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + Avoid::Point& a0 = conn.ps[cIndex - 2]; + + //db_printf("a0: %g %g\n", a0.x, a0.y); + //db_printf("b0: %g %g\n", b0.x, b0.y); + + if ((a0 == b2) || (a0 == b0)) + { + // Determine direction we have to look through + // the points of connector b. + p_dir_back = (a0 == b0) ? true : false; + p_dir = p_dir_back ? -1 : 1; + trace_c = (int) cIndex; + trace_p = (int) (p_dir_back ? j : j - 2); + + shared_path = true; + } + } + + if (shared_path) + { + crossingFlags |= CROSSING_SHARES_PATH; + // Shouldn't be here if p_dir is still equal to zero. + COLA_ASSERT(p_dir != 0); + + // Build the shared path, including the diverging points at + // each end if the connector does not end at a common point. + while ( (trace_c >= 0) && (!polyIsConn || + ((trace_p >= 0) && (trace_p < (int) poly_size))) ) + { + // If poly is a cluster boundary, then it is a closed + // poly-line and so it wraps arounds. + size_t index_p = (size_t) + ((trace_p + (2 * poly_size)) % poly_size); + size_t index_c = (size_t) trace_c; + c_path.push_back(&conn.ps[index_c]); + p_path.push_back(&poly.ps[index_p]); + if ((c_path.size() > 1) && + (conn.ps[index_c] != poly.ps[index_p])) + { + // Points don't match, so break out of loop. + break; + } + trace_c--; + trace_p += p_dir; + } + + // Are there diverging points at the ends of the shared path. + bool front_same = (*(c_path.front()) == *(p_path.front())); + bool back_same = (*(c_path.back()) == *(p_path.back())); + + size_t size = c_path.size(); + + // Check to see if these share a fixed segment. + if (polyIsOrthogonal && connIsOrthogonal) + { + size_t startPt = (front_same) ? 0 : 1; + if (c_path[startPt]->x == c_path[startPt + 1]->x) + { + // Vertical + double xPos = c_path[startPt]->x; + // See if this is inline with either the start + // or end point of both connectors. + if ( ((xPos == poly.ps[0].x) || + (xPos == poly.ps[poly_size - 1].x)) && + ((xPos == conn.ps[0].x) || + (xPos == conn.ps[cIndex].x)) ) + { + crossingFlags |= CROSSING_SHARES_FIXED_SEGMENT; + } + } + else + { + // Horizontal + double yPos = c_path[startPt]->y; + // See if this is inline with either the start + // or end point of both connectors. + if ( ((yPos == poly.ps[0].y) || + (yPos == poly.ps[poly_size - 1].y)) && + ((yPos == conn.ps[0].y) || + (yPos == conn.ps[cIndex].y)) ) + { + crossingFlags |= CROSSING_SHARES_FIXED_SEGMENT; + } + } + } + + int prevTurnDir = -1; + int startCornerSide = 1; + int endCornerSide = 1; + bool reversed = false; + if (!front_same) + { + // If there is a divergence at the beginning, + // then order the shared path based on this. + prevTurnDir = vecDir(*c_path[0], *c_path[1], *c_path[2]); + startCornerSide = Avoid::cornerSide(*c_path[0], *c_path[1], + *c_path[2], *p_path[0]) + * segDir(*c_path[1], *c_path[2]); + reversed = (startCornerSide != -prevTurnDir); + } + if (!back_same) + { + // If there is a divergence at the end of the path, + // then order the shared path based on this. + prevTurnDir = vecDir(*c_path[size - 3], + *c_path[size - 2], *c_path[size - 1]); + endCornerSide = Avoid::cornerSide(*c_path[size - 3], + *c_path[size - 2], *c_path[size - 1], + *p_path[size - 1]) + * segDir(*c_path[size - 3], *c_path[size - 2]); + reversed = (endCornerSide != -prevTurnDir); + } + else + { + endCornerSide = startCornerSide; + } + if (front_same) + { + startCornerSide = endCornerSide; + } + + if (front_same || back_same) + { + crossingFlags |= CROSSING_SHARES_PATH_AT_END; + } + else if (polyIsOrthogonal && connIsOrthogonal) + { + int cStartDir = vecDir(*c_path[0], *c_path[1], *c_path[2]); + int pStartDir = vecDir(*p_path[0], *p_path[1], *p_path[2]); + if ((cStartDir != 0) && (cStartDir == -pStartDir)) + { + // The start segments diverge at 180 degrees to each + // other. So order based on not introducing overlap + // of the diverging segments when these are nudged + // apart. + startCornerSide = -cStartDir * + segDir(*c_path[1], *c_path[2]); + } + else + { + int cEndDir = vecDir(*c_path[size - 3], + *c_path[size - 2], *c_path[size - 1]); + int pEndDir = vecDir(*p_path[size - 3], + *p_path[size - 2], *p_path[size - 1]); + if ((cEndDir != 0) && (cEndDir == -pEndDir)) + { + // The end segments diverge at 180 degrees to + // each other. So order based on not introducing + // overlap of the diverging segments when these + // are nudged apart. + startCornerSide = -cEndDir * segDir( + *c_path[size - 3], *c_path[size - 2]); + } + } + } + +#if 0 + prevTurnDir = 0; + if (pointOrders) + { + // Return the ordering for the shared path. + COLA_ASSERT(c_path.size() > 0 || back_same); + size_t adj_size = (c_path.size() - ((back_same) ? 0 : 1)); + for (size_t i = (front_same) ? 0 : 1; i < adj_size; ++i) + { + Avoid::Point& an = *(c_path[i]); + Avoid::Point& bn = *(p_path[i]); + int currTurnDir = ((i > 0) && (i < (adj_size - 1))) ? + vecDir(*c_path[i - 1], an, + *c_path[i + 1]) : 0; + VertID vID(an.id, true, an.vn); + if ( (currTurnDir == (-1 * prevTurnDir)) && + (currTurnDir != 0) && (prevTurnDir != 0) ) + { + // The connector turns the opposite way around + // this shape as the previous bend on the path, + // so reverse the order so that the inner path + // become the outer path and vice versa. + reversed = !reversed; + } + bool orderSwapped = (*pointOrders)[an].addPoints( + &bn, &an, reversed); + if (orderSwapped) + { + // Reverse the order for later points. + reversed = !reversed; + } + prevTurnDir = currTurnDir; + } + } +#endif + prevTurnDir = 0; + if (pointOrders) + { + reversed = false; + size_t startPt = (front_same) ? 0 : 1; + + // Orthogonal should always have at least one segment. + COLA_ASSERT(c_path.size() > (startPt + 1)); + + if (startCornerSide > 0) + { + reversed = !reversed; + } + + int prevDir = 0; + // Return the ordering for the shared path. + COLA_ASSERT(c_path.size() > 0 || back_same); + size_t adj_size = (c_path.size() - ((back_same) ? 0 : 1)); + for (size_t i = (front_same) ? 0 : 1; i < adj_size; ++i) + { + Avoid::Point& an = *(c_path[i]); + Avoid::Point& bn = *(p_path[i]); + COLA_ASSERT(an == bn); + + int thisDir = prevDir; + if ((i > 0) && (*(c_path[i - 1]) == *(p_path[i - 1]))) + { + thisDir = segDir(*c_path[i - 1], *c_path[i]); + } + + if (thisDir != prevDir) + { + reversed = !reversed; + } + prevDir = thisDir; + + if (i > startPt) + { + Avoid::Point& ap = *(c_path[i - 1]); + Avoid::Point& bp = *(p_path[i - 1]); + int orientation = (ap.x == an.x) ? 0 : 1; + //printf("prevOri %d\n", prevOrientation); + //printf("1: %X, %X\n", (int) &(bn), (int) &(an)); + bool orderSwapped = (*pointOrders)[an].addPoints( + orientation, + std::make_pair(&bn, polyConnRef), + std::make_pair(&an, connConnRef), + reversed); + if (orderSwapped) + { + // Reverse the order for later points. + reversed = !reversed; + } + COLA_ASSERT(ap == bp); + //printf("2: %X, %X\n", (int) &bp, (int) &ap); + orderSwapped = (*pointOrders)[ap].addPoints( + orientation, + std::make_pair(&bp, polyConnRef), + std::make_pair(&ap, connConnRef), + reversed); + COLA_ASSERT(!orderSwapped); + } + } + } +#if 0 + int ymod = -1; + if ((id.vn == 1) || (id.vn == 2)) + { + // bottom. + ymod = +1; + } + + int xmod = -1; + if ((id.vn == 0) || (id.vn == 1)) + { + // right. + xmod = +1; + } + if(id.vn > 3) + { + xmod = ymod = 0; + if (id.vn == 4) + { + // right. + xmod = +1; + } + else if (id.vn == 5) + { + // bottom. + ymod = +1; + } + else if (id.vn == 6) + { + // left. + xmod = -1; + } + else if (id.vn == 7) + { + // top. + ymod = -1; + } + } +#endif + + if (endCornerSide != startCornerSide) + { + // Mark that the shared path crosses. + //db_printf("shared path crosses.\n"); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(*c_path[1]); + } + } + crossingFlags |= CROSSING_TOUCHES; + } + else if (cIndex >= 2) + { + // The connectors cross or touch at this point. + //db_printf("Cross or touch at point... \n"); + + // Crossing shouldn't be at an endpoint. + COLA_ASSERT(cIndex >= 2); + COLA_ASSERT(polyIsConn && (j >= 2)); + + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + Avoid::Point& a0 = conn.ps[cIndex - 2]; + + int side1 = Avoid::cornerSide(a0, a1, a2, b0); + int side2 = Avoid::cornerSide(a0, a1, a2, b2); + if (side1 != side2) + { + // The connectors cross at this point. + //db_printf("cross.\n"); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(a1); + } + } + + crossingFlags |= CROSSING_TOUCHES; + if (pointOrders) + { + if (polyIsOrthogonal && connIsOrthogonal) + { + // Orthogonal case: + // Just order based on which comes from the left and + // top in each dimension because this can only be two + // L-shaped segments touching at the bend. + bool reversedX = ((a0.x < a1.x) || (a2.x < a1.x)); + bool reversedY = ((a0.y < a1.y) || (a2.y < a1.y)); + // XXX: Why do we need to invert the reversed values + // here? Are they wrong for orthogonal points + // in the other places? + (*pointOrders)[b1].addPoints(0, + std::make_pair(&b1, polyConnRef), + std::make_pair(&a1, connConnRef), + !reversedX); + (*pointOrders)[b1].addPoints(1, + std::make_pair(&b1, polyConnRef), + std::make_pair(&a1, connConnRef), + !reversedY); + } + else + { + int turnDirA = vecDir(a0, a1, a2); + int turnDirB = vecDir(b0, b1, b2); + bool reversed = (side1 != -turnDirA); + if (side1 != side2) + { + // Interesting case where a connector routes round + // the edge of a shape and intersects a connector + // which is connected to a port on the edge of the + // shape. + if (turnDirA == 0) + { + // We'll make B the outer by preference, + // because the points of A are collinear. + reversed = false; + } + else if (turnDirB == 0) + { + reversed = true; + } + // TODO COLA_ASSERT((turnDirB != 0) || + // (turnDirA != 0)); + } + VertID vID(b1.id, true, b1.vn); + //(*pointOrders)[b1].addPoints(&b1, &a1, reversed); + } + } + } + } + else + { + if ( polyIsOrthogonal && connIsOrthogonal) + { + // All crossings in orthogonal connectors will be at a + // vertex in the visibility graph, so we need not bother + // doing normal line intersection. + continue; + } + + // No endpoint is shared between these two line segments, + // so just calculate normal segment intersection. + + Point cPt; + int intersectResult = Avoid::segmentIntersectPoint( + a1, a2, b1, b2, &(cPt.x), &(cPt.y)); + + if (intersectResult == Avoid::DO_INTERSECT) + { + if (!polyIsConn && + ((a1 == cPt) || (a2 == cPt) || (b1 == cPt) || (b2 == cPt))) + { + // XXX: This shouldn't actually happen, because these + // points should be added as bends to each line by + // splitBranchingSegments(). Thus, lets ignore them. + COLA_ASSERT(a1 != cPt); + COLA_ASSERT(a2 != cPt); + COLA_ASSERT(b1 != cPt); + COLA_ASSERT(b2 != cPt); + continue; + } + //db_printf("crossing lines:\n"); + //db_printf("cPt: %g %g\n", cPt.x, cPt.y); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(cPt); + } + } + } + } + //db_printf("crossingcount %d\n", crossingCount); + return std::make_pair(crossingCount, crossingFlags); +} + + //============================================================================ } diff --git a/src/libavoid/connector.h b/src/libavoid/connector.h index 64afb4dda..8f7499a29 100644 --- a/src/libavoid/connector.h +++ b/src/libavoid/connector.h @@ -2,93 +2,336 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ +//! @file shape.h +//! @brief Contains the interface for the ConnRef class. + + #ifndef AVOID_CONNECTOR_H #define AVOID_CONNECTOR_H -#include "libavoid/router.h" +#include <list> +#include <vector> + +#include "libavoid/vertices.h" #include "libavoid/geometry.h" #include "libavoid/shape.h" -#include <list> namespace Avoid { +class Router; +class ConnRef; +typedef std::list<ConnRef *> ConnRefList; + + +//! @brief Describes the type of routing that is performed for each +//! connector. +enum ConnType { + ConnType_None = 0, + //! @brief The connector path will be a shortest-path poly-line that + //! routes around obstacles. + ConnType_PolyLine = 1, + //! @brief The connector path will be a shortest-path orthogonal + //! poly-line (only vertical and horizontal line segments) that + //! routes around obstacles. + ConnType_Orthogonal = 2 +}; + +//! @brief Flags that can be passed to the ConnEnd constructor to specify +//! which sides of a shape this point should have visibility to if +//! it is located within the shape's area. +//! +//! Like SVG, libavoid considers the Y-axis to point downwards, that is, +//! like screen coordinates the coordinates increase from left-to-right and +//! also from top-to-bottom. +//! +enum ConnDirFlag { + ConnDirNone = 0, + //! @brief This option specifies the point should be given visibility + //! to the top of the shape that it is located within. + ConnDirUp = 1, + //! @brief This option specifies the point should be given visibility + //! to the bottom of the shape that it is located within. + ConnDirDown = 2, + //! @brief This option specifies the point should be given visibility + //! to the left side of the shape that it is located within. + ConnDirLeft = 4, + //! @brief This option specifies the point should be given visibility + //! to the right side of the shape that it is located within. + ConnDirRight = 8, + //! @brief This option, provided for convenience, specifies the point + //! should be given visibility to all four sides of the shape + //! that it is located within. + ConnDirAll = 15 +}; +//! @brief One or more Avoid::ConnDirFlag options. +//! +typedef unsigned int ConnDirFlags; + + +static const double ATTACH_POS_TOP = 0; +static const double ATTACH_POS_CENTER = 0.5; +static const double ATTACH_POS_BOTTOM = 1; +static const double ATTACH_POS_LEFT = ATTACH_POS_TOP; +static const double ATTACH_POS_RIGHT = ATTACH_POS_BOTTOM; + + +//! @brief The ConnEnd class represents different possible endpoints for +//! connectors. +//! +//! Currently this class just allows free-floating endpoints, but in future +//! will be capable of representing attachments to connection points on shapes. +//! +class ConnEnd +{ + public: + //! @brief Constructs a ConnEnd from a free-floating point. + //! + //! @param[in] point The position of the connector endpoint. + //! + ConnEnd(const Point& point); + + //! @brief Constructs a ConnEnd from a free-floating point as well + //! as a set of flags specifying visibility for this point + //! if it is located inside a shape. + //! + //! @param[in] point The position of the connector endpoint. + //! @param[in] visDirs One or more Avoid::ConnDirFlag options + //! specifying the directions that this point + //! should be given visibility if it is inside + //! a shape. + //! + ConnEnd(const Point& point, const ConnDirFlags visDirs); + + ConnEnd(ShapeRef *shapeRef, const double x_pos, const double y_pos, + const double insideOffset = 0.0, + const ConnDirFlags visDirs = ConnDirNone); -static const int ConnType_PolyLine = 1; -static const int ConnType_Orthogonal = 2; + //! @brief Returns the position of this connector endpoint + //! + //! @return The position of this connector endpoint. + const Point point(void) const; + + ConnDirFlags directions(void) const; + private: + Point _point; + ConnDirFlags _directions; + + // For referencing ConnEnds + ShapeRef *_shapeRef; + double _xPosition; + double _yPosition; + double _insideOffset; +}; +//! @brief The ConnRef class represents a connector object. +//! +//! Connectors are a (possible multi-segment) line between two points. +//! They are routed intelligently so as not to overlap any of the shape +//! objects in the Router scene. +//! +//! Routing penalties can be applied, resulting in more aesthetically pleasing +//! connector paths with fewer segments or less severe bend-points. +//! +//! You can set a function to be called when the connector has been rerouted +//! and needs to be redrawn. Alternatively, you can query the connector's +//! needsRepaint() function to determine this manually. +//! +//! Usually, it is expected that you would create a ConnRef for each connector +//! in your diagram and keep that reference in your own connector class. +//! class ConnRef { public: - ConnRef(Router *router, const unsigned int id); - ConnRef(Router *router, const unsigned int id, - const Point& src, const Point& dst); - virtual ~ConnRef(); + //! @brief Constructs a connector with no endpoints specified. + //! + //! @param[in] router The router scene to place the connector into. + //! @param[in] id A unique positive integer ID for the connector. + //! + //! If an ID is not specified, then one will be assigned to the shape. + //! If assigning an ID yourself, note that it should be a unique + //! positive integer. Also, IDs are given to all objects in a scene, + //! so the same ID cannot be given to a shape and a connector for + //! example. + //! + ConnRef(Router *router, const unsigned int id = 0); + //! @brief Constructs a connector with endpoints specified. + //! + //! @param[in] router The router scene to place the connector into. + //! @param[in] id A unique positive integer ID for the connector. + //! @param[in] src The source endpoint of the connector. + //! @param[in] dst The destination endpoint of the connector. + //! + //! If an ID is not specified, then one will be assigned to the shape. + //! If assigning an ID yourself, note that it should be a unique + //! positive integer. Also, IDs are given to all objects in a scene, + //! so the same ID cannot be given to a shape and a connector for + //! example. + //! + ConnRef(Router *router, const ConnEnd& src, const ConnEnd& dst, + const unsigned int id = 0); + //! @brief Destuctor. + ~ConnRef(); - void setType(unsigned int type); - PolyLine& route(void); - bool needsReroute(void); - void freeRoute(void); + //! @brief Sets both new source and destination endpoints for this + //! connector. + //! + //! @param[in] srcPoint New source endpoint for the connector. + //! @param[in] dstPoint New destination endpoint for the connector. + void setEndpoints(const ConnEnd& srcPoint, const ConnEnd& dstPoint); + //! @brief Sets just a new source endpoint for this connector. + //! + //! @param[in] srcPoint New source endpoint for the connector. + void setSourceEndpoint(const ConnEnd& srcPoint); + //! @brief Sets just a new destination endpoint for this connector. + //! + //! @param[in] dstPoint New destination endpoint for the connector. + void setDestEndpoint(const ConnEnd& dstPoint); + //! @brief Returns the ID of this connector. + //! @returns The ID of the connector. + unsigned int id(void) const; + //! @brief Returns a pointer to the router scene this connector is in. + //! @returns A pointer to the router scene for this connector. + Router *router(void) const; + + //! @brief Returns an indication of whether this connector has a + //! new route and thus needs to be repainted. + //! + //! If the connector has been rerouted and need repainting, the + //! route() method can be called to get a reference to the new route. + //! + //! @returns Returns true if the connector requires repainting, or + //! false if it does not. + bool needsRepaint(void) const; + + //! @brief Returns a reference to the current route for the connector. + //! + //! This is a "raw" version of the route, where each line segment in + //! the route may be made up of multiple collinear line segments. It + //! will also not have post-processing (like curved corners) applied + //! to it. The simplified route for display can be obtained by calling + //! displayRoute(). + //! + //! @returns The PolyLine route for the connector. + //! @note You can obtain a modified version of this poly-line + //! route with curved corners added by calling + //! PolyLine::curvedPolyline(). + const PolyLine& route(void) const; + + //! @brief Returns a reference to the current display version of the + //! route for the connector. + //! + //! The display version of a route has been simplified to collapse all + //! collinear line segments into single segments. It may also have + //! post-processing applied to the route, such as curved corners or + //! nudging. + //! + //! @returns The PolyLine display route for the connector. + PolyLine& displayRoute(void); + + //! @brief Sets a callback function that will called to indicate that + //! the connector needs rerouting. + //! + //! The cb function will be called when shapes are added to, removed + //! from or moved about on the page. The pointer ptr will be passed + //! as an argument to the callback function. + //! + //! @param[in] cb A pointer to the callback function. + //! @param[in] ptr A generic pointer that will be passed to the + //! callback function. + void setCallback(void (*cb)(void *), void *ptr); + //! @brief Returns the type of routing performed for this connector. + //! @return The type of routing performed. + //! + ConnType routingType(void) const; + //! @brief Sets the type of routing to be performed for this + //! connector. + //! + //! If a call to this method changes the current type of routing + //! being used for the connector, then it will get rerouted during + //! the next processTransaction() call, or immediately if + //! transactions are not being used. + //! + //! @param type The type of routing to be performed. + //! + void setRoutingType(ConnType type); + + + + // @brief Returns the source endpoint vertex in the visibility graph. + // @returns The source endpoint vertex. + VertInf *src(void); + // @brief Returns the destination endpoint vertex in the + // visibility graph. + // @returns The destination endpoint vertex. + VertInf *dst(void); + + + void set_route(const PolyLine& route); void calcRouteDist(void); - void updateEndPoint(const unsigned int type, const Point& point); void setEndPointId(const unsigned int type, const unsigned int id); unsigned int getSrcShapeId(void); unsigned int getDstShapeId(void); void makeActive(void); void makeInactive(void); - void lateSetup(const Point& src, const Point& dst); - unsigned int id(void); - VertInf *src(void); - VertInf *dst(void); + VertInf *start(void); void removeFromGraph(void); bool isInitialised(void); - void unInitialise(void); - void setCallback(void (*cb)(void *), void *ptr); - void handleInvalid(void); - int generatePath(Point p0, Point p1); void makePathInvalid(void); - Router *router(void); void setHateCrossings(bool value); bool doesHateCrossings(void); - - friend void Router::attachedShapes(IntList &shapes, - const unsigned int shapeId, const unsigned int type); - friend void Router::attachedConns(IntList &conns, - const unsigned int shapeId, const unsigned int type); - friend void Router::markConnectors(ShapeRef *shape); - + void setEndpoint(const unsigned int type, const ConnEnd& connEnd); + bool setEndpoint(const unsigned int type, const VertID& pointID, + Point *pointSuggestion = NULL); + private: + friend class Router; + + PolyLine& routeRef(void); + void freeRoutes(void); + void performCallback(void); + bool generatePath(void); + bool generatePath(Point p0, Point p1); + void unInitialise(void); + void updateEndPoint(const unsigned int type, const ConnEnd& connEnd); + void common_updateEndPoint(const unsigned int type, const ConnEnd& connEnd); Router *_router; unsigned int _id; - unsigned int _type; + ConnType _type; unsigned int _srcId, _dstId; + bool _orthogonal; bool _needs_reroute_flag; bool _false_path; + bool _needs_repaint; bool _active; PolyLine _route; + Polygon _display_route; double _route_dist; ConnRefList::iterator _pos; VertInf *_srcVert; VertInf *_dstVert; + VertInf *_startVert; bool _initialised; void (*_callback)(void *); void *_connector; @@ -96,6 +339,69 @@ class ConnRef }; +class PointRep; +typedef std::set<PointRep *> PointRepSet; +typedef std::list<PointRep *> PointRepList; + +class PointRep +{ + public: + PointRep(Point *p, const ConnRef *c) + : point(p), + conn(c) + + { + } + bool follow_inner(PointRep *target); + + Point *point; + const ConnRef *conn; + // inner_set: Set of pointers to the PointReps 'inner' of + // this one, at this corner. + PointRepSet inner_set; +}; + + +typedef std::pair<Point *, ConnRef *> PtConnPtrPair; + +class PtOrder +{ + public: + PtOrder() + { + } + ~PtOrder(); + bool addPoints(const int dim, PtConnPtrPair innerArg, + PtConnPtrPair outerArg, bool swapped); + void sort(const int dim); + int positionFor(const ConnRef *conn, const size_t dim) const; + + // One for each dimension. + PointRepList connList[2]; +}; + +typedef std::map<Avoid::Point,PtOrder> PtOrderMap; +typedef std::set<Avoid::Point> PointSet; + + +const unsigned int CROSSING_NONE = 0; +const unsigned int CROSSING_TOUCHES = 1; +const unsigned int CROSSING_SHARES_PATH = 2; +const unsigned int CROSSING_SHARES_PATH_AT_END = 4; +const unsigned int CROSSING_SHARES_FIXED_SEGMENT = 8; + + +typedef std::pair<int, unsigned int> CrossingsInfoPair; + +extern CrossingsInfoPair countRealCrossings( Avoid::Polygon& poly, + bool polyIsConn, Avoid::Polygon& conn, size_t cIndex, + bool checkForBranchingSegments, const bool finalSegment = false, + PointSet *crossingPoints = NULL, PtOrderMap *pointOrders = NULL, + ConnRef *polyConnRef = NULL, ConnRef *connConnRef = NULL); +extern void splitBranchingSegments(Avoid::Polygon& poly, bool polyIsConn, + Avoid::Polygon& conn, const double tolerance = 0); +extern bool validateBendPoint(VertInf *aInf, VertInf *bInf, VertInf *cInf); + } diff --git a/src/libavoid/debug.h b/src/libavoid/debug.h index 20e6f4705..443529ece 100644 --- a/src/libavoid/debug.h +++ b/src/libavoid/debug.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef AVOID_DEBUG_H #define AVOID_DEBUG_H @@ -42,7 +45,7 @@ inline void db_printf(const char *fmt, ...) va_end(ap); } #else -inline void db_printf(const char */*fmt*/, ...) +inline void db_printf(const char *, ...) { } #endif diff --git a/src/libavoid/geometry.cpp b/src/libavoid/geometry.cpp index 15840c381..2523375cf 100644 --- a/src/libavoid/geometry.cpp +++ b/src/libavoid/geometry.cpp @@ -2,7 +2,8 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * -------------------------------------------------------------------- * Much of the code in this module is based on code published with @@ -18,70 +19,42 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + +#include <cmath> + #include "libavoid/graph.h" #include "libavoid/geometry.h" -#include "libavoid/polyutil.h" - -#include <math.h> +#include "libavoid/assertions.h" namespace Avoid { -Point::Point() -{ -} - - -Point::Point(const double xv, const double yv) - : x(xv) - , y(yv) -{ -} - - -bool Point::operator==(const Point& rhs) const -{ - if ((x == rhs.x) && (y == rhs.y)) - { - return true; - } - return false; -} - - -bool Point::operator!=(const Point& rhs) const -{ - if ((x != rhs.x) || (y != rhs.y)) - { - return true; - } - return false; -} - // Returns true iff the point c lies on the closed segment ab. +// To be used when the points are known to be collinear. // // Based on the code of 'Between'. // -static const bool inBetween(const Point& a, const Point& b, const Point& c) +bool inBetween(const Point& a, const Point& b, const Point& c) { // We only call this when we know the points are collinear, // otherwise we should be checking this here. - assert(vecDir(a, b, c) == 0); + COLA_ASSERT(vecDir(a, b, c, 0.0001) == 0); - if (a.x != b.x) + if ((fabs(a.x - b.x) > 1) && (a.x != b.x)) { // not vertical return (((a.x < c.x) && (c.x < b.x)) || @@ -95,6 +68,15 @@ static const bool inBetween(const Point& a, const Point& b, const Point& c) } +// Returns true iff the point c lies on the closed segment ab. +// +bool pointOnLine(const Point& a, const Point& b, const Point& c, + const double tolerance) +{ + return (vecDir(a, b, c, tolerance) == 0) && inBetween(a, b, c); +} + + // Returns true if the segment cd intersects the segment ab, blocking // visibility. // @@ -104,15 +86,15 @@ bool segmentIntersect(const Point& a, const Point& b, const Point& c, const Point& d) { int ab_c = vecDir(a, b, c); - if ((ab_c == 0) && inBetween(a, b, c)) + if (ab_c == 0) { - return true; + return false; } int ab_d = vecDir(a, b, d); - if ((ab_d == 0) && inBetween(a, b, d)) + if (ab_d == 0) { - return true; + return false; } // It's ok for either of the points a or b to be on the line cd, @@ -131,6 +113,37 @@ bool segmentIntersect(const Point& a, const Point& b, const Point& c, } +// Returns true if the segment e1-e2 intersects the shape boundary +// segment s1-s2, blocking visibility. +// +bool segmentShapeIntersect(const Point& e1, const Point& e2, const Point& s1, + const Point& s2, bool& seenIntersectionAtEndpoint) +{ + if (segmentIntersect(e1, e2, s1, s2)) + { + // Basic intersection of segments. + return true; + } + else if ( (((s2 == e1) || pointOnLine(s1, s2, e1)) && + (vecDir(s1, s2, e2) != 0)) + || + (((s2 == e2) || pointOnLine(s1, s2, e2)) && + (vecDir(s1, s2, e1) != 0)) ) + { + // Segments intersect at the endpoint of one of the segments. We + // allow this once, but the second one blocks visibility. Otherwise + // shapes butted up against each other could have visibility through + // shapes. + if (seenIntersectionAtEndpoint) + { + return true; + } + seenIntersectionAtEndpoint = true; + } + return false; +} + + // Returns true iff the point p in a valid region that can contain // shortest paths. a0, a1, a2 are ordered vertices of a shape. // @@ -205,20 +218,9 @@ int cornerSide(const Point &c1, const Point &c2, const Point &c3, int s12p = vecDir(c1, c2, p); int s23p = vecDir(c2, c3, p); - if (s12p == 0) - { - // Case of p being somewhere on c1-c2. - return s23p; - } - if (s23p == 0) - { - // Case of p being somewhere on c2-c3. - return s12p; - } - if (s123 == 1) { - if ((s12p == 1) && (s23p == 1)) + if ((s12p >= 0) && (s23p >= 0)) { return 1; } @@ -226,18 +228,37 @@ int cornerSide(const Point &c1, const Point &c2, const Point &c3, } else if (s123 == -1) { - if ((s12p == -1) && (s23p == -1)) + if ((s12p <= 0) && (s23p <= 0)) { return -1; } return 1; } - // Case of c3 being somewhere on c1-c2. + + // c1-c2-c3 are collinear, so just return vecDir from c1-c2 return s12p; } -// Returns the distance between points a and b. +// Returns the Euclidean distance between points a and b. +// +double euclideanDist(const Point& a, const Point& b) +{ + double xdiff = a.x - b.x; + double ydiff = a.y - b.y; + + return sqrt((xdiff * xdiff) + (ydiff * ydiff)); +} + +// Returns the Manhattan distance between points a and b. +// +double manhattanDist(const Point& a, const Point& b) +{ + return fabs(a.x - b.x) + fabs(a.y - b.y); +} + + +// Returns the Euclidean distance between points a and b. // double dist(const Point& a, const Point& b) { @@ -248,11 +269,12 @@ double dist(const Point& a, const Point& b) } // Returns the total length of all line segments in the polygon -double totalLength(const Polygn& poly) +double totalLength(const Polygon& poly) { double l = 0; - for (int i = 0; i < poly.pn-1; ++i) { - l += dist(poly.ps[i], poly.ps[i+1]); + for (size_t i = 1; i < poly.size(); ++i) + { + l += dist(poly.ps[i-1], poly.ps[i]); } return l; } @@ -277,18 +299,27 @@ double angle(const Point& a, const Point& b, const Point& c) // This is a fast version that only works for convex shapes. The // other version (inPolyGen) is more general. // -bool inPoly(const Polygn& poly, const Point& q) +bool inPoly(const Polygon& poly, const Point& q, bool countBorder) { - int n = poly.pn; - Point *P = poly.ps; - for (int i = 0; i < n; i++) + size_t n = poly.size(); + const std::vector<Point>& P = poly.ps; + bool onBorder = false; + for (size_t i = 0; i < n; i++) { // point index; i1 = i-1 mod n - int prev = (i + n - 1) % n; - if (vecDir(P[prev], P[i], q) == -1) + size_t prev = (i + n - 1) % n; + int dir = vecDir(P[prev], P[i], q); + if (dir == -1) { + // Point is outside return false; } + // Record if point was on a boundary. + onBorder |= (dir == 0); + } + if (!countBorder && onBorder) + { + return false; } return true; } @@ -299,37 +330,36 @@ bool inPoly(const Polygn& poly, const Point& q) // // Based on the code of 'InPoly'. // -bool inPolyGen(const Polygn& argpoly, const Point& q) +bool inPolyGen(const PolygonInterface& argpoly, const Point& q) { // Numbers of right and left edge/ray crossings. int Rcross = 0; int Lcross = 0; // Copy the argument polygon - Polygn poly = copyPoly(argpoly); - Point *P = poly.ps; - int n = poly.pn; + Polygon poly = argpoly; + std::vector<Point>& P = poly.ps; + size_t n = poly.size(); // Shift so that q is the origin. This is done for pedogical clarity. - for (int i = 0; i < n; ++i) + for (size_t i = 0; i < n; ++i) { P[i].x = P[i].x - q.x; P[i].y = P[i].y - q.y; } // For each edge e=(i-1,i), see if crosses ray. - for (int i = 0; i < n; ++i) + for (size_t i = 0; i < n; ++i) { // First see if q=(0,0) is a vertex. if ((P[i].x == 0) && (P[i].y == 0)) { // We count a vertex as inside. - freePoly(poly); return true; } // point index; i1 = i-1 mod n - int i1 = ( i + n - 1 ) % n; + size_t i1 = ( i + n - 1 ) % n; // if e "straddles" the x-axis... // The commented-out statement is logically equivalent to the one @@ -367,7 +397,6 @@ bool inPolyGen(const Polygn& argpoly, const Point& q) } } } - freePoly(poly); // q on the edge if left and right cross are not the same parity. if ( (Rcross % 2) != (Lcross % 2) ) @@ -400,8 +429,7 @@ bool inPolyGen(const Polygn& argpoly, const Point& q) int segmentIntersectPoint(const Point& a1, const Point& a2, const Point& b1, const Point& b2, double *x, double *y) { - - double Ax,Bx,Cx,Ay,By,Cy,d,e,f,num,offset; + double Ax,Bx,Cx,Ay,By,Cy,d,e,f,num; double x1lo,x1hi,y1lo,y1hi; Ax = a2.x - a1.x; @@ -450,14 +478,13 @@ int segmentIntersectPoint(const Point& a1, const Point& a2, if (y1hi < b1.y || b2.y < y1lo) return DONT_INTERSECT; } - Cx = a1.x - b1.x; Cy = a1.y - b1.y; // alpha numerator: d = By*Cx - Bx*Cy; // Both denominator: f = Ay*Bx - Ax*By; - // aplha tests: + // alpha tests: if (f > 0) { if (d < 0 || d > f) return DONT_INTERSECT; @@ -485,15 +512,49 @@ int segmentIntersectPoint(const Point& a1, const Point& a2, // Numerator: num = d*Ax; - // Round direction: - offset = SAME_SIGNS(num,f) ? f/2 : -f/2; // Intersection X: - *x = a1.x + (num+offset) / f; + *x = a1.x + (num) / f; + + num = d*Ay; + // Intersection Y: + *y = a1.y + (num) / f; + + return DO_INTERSECT; +} + + +// Line Segment Intersection +// Original code by Franklin Antonio +// +int rayIntersectPoint(const Point& a1, const Point& a2, + const Point& b1, const Point& b2, double *x, double *y) +{ + double Ax,Bx,Cx,Ay,By,Cy,d,f,num; + + Ay = a2.y - a1.y; + By = b1.y - b2.y; + Ax = a2.x - a1.x; + Bx = b1.x - b2.x; + + Cx = a1.x - b1.x; + Cy = a1.y - b1.y; + // alpha numerator: + d = By*Cx - Bx*Cy; + // Both denominator: + f = Ay*Bx - Ax*By; + + // compute intersection coordinates: + + if (f == 0) return PARALLEL; + + // Numerator: + num = d*Ax; + // Intersection X: + *x = a1.x + (num) / f; num = d*Ay; - offset = SAME_SIGNS(num,f) ? f/2 : -f/2; // Intersection Y: - *y = a1.y + (num+offset) / f; + *y = a1.y + (num) / f; return DO_INTERSECT; } diff --git a/src/libavoid/geometry.h b/src/libavoid/geometry.h index 1422be050..abd0d60e2 100644 --- a/src/libavoid/geometry.h +++ b/src/libavoid/geometry.h @@ -2,7 +2,8 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * -------------------------------------------------------------------- * Much of the code in this module is based on code published with @@ -18,16 +19,17 @@ * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ @@ -35,21 +37,30 @@ #define _GEOMETRY_H #include "libavoid/geomtypes.h" +#include "libavoid/assertions.h" namespace Avoid { -extern double dist(const Point& a, const Point& b); -extern double totalLength(const Polygn& poly); +extern double euclideanDist(const Point& a, const Point& b); +extern double manhattanDist(const Point& a, const Point& b); +extern double totalLength(const Polygon& poly); extern double angle(const Point& a, const Point& b, const Point& c); extern bool segmentIntersect(const Point& a, const Point& b, const Point& c, const Point& d); -extern bool inPoly(const Polygn& poly, const Point& q); -extern bool inPolyGen(const Polygn& poly, const Point& q); +extern bool segmentShapeIntersect(const Point& e1, const Point& e2, + const Point& s1, const Point& s2, bool& seenIntersectionAtEndpoint); +extern bool inPoly(const Polygon& poly, const Point& q, bool countBorder = true); +extern bool inPolyGen(const PolygonInterface& poly, const Point& q); extern bool inValidRegion(bool IgnoreRegions, const Point& a0, const Point& a1, const Point& a2, const Point& b); extern int cornerSide(const Point &c1, const Point &c2, const Point &c3, const Point& p); +extern bool pointOnLine(const Point& a, const Point& b, const Point& c, + const double tolerance = 0.0); + +// To be used only when the points are known to be colinear. +extern bool inBetween(const Point& a, const Point& b, const Point& c); // Direction from vector. @@ -61,16 +72,22 @@ extern int cornerSide(const Point &c1, const Point &c2, const Point &c3, // // Based on the code of 'AreaSign'. // -static inline int vecDir(const Point& a, const Point& b, const Point& c) +// The 'maybeZero' argument can be used to adjust the tolerance of the +// function. It will be most accurate when 'maybeZero' == 0.0, the default. +// +static inline int vecDir(const Point& a, const Point& b, const Point& c, + const double maybeZero = 0.0) { + COLA_ASSERT(maybeZero >= 0); + double area2 = ((b.x - a.x) * (c.y - a.y)) - ((c.x - a.x) * (b.y - a.y)); - if (area2 < -0.001) + if (area2 < (-maybeZero)) { return -1; } - else if (area2 > 0.001) + else if (area2 > maybeZero) { return 1; } @@ -100,6 +117,8 @@ static const int DO_INTERSECT = 1; static const int PARALLEL = 3; extern int segmentIntersectPoint(const Point& a1, const Point& a2, const Point& b1, const Point& b2, double *x, double *y); +extern int rayIntersectPoint(const Point& a1, const Point& a2, + const Point& b1, const Point& b2, double *x, double *y); } diff --git a/src/libavoid/geomtypes.cpp b/src/libavoid/geomtypes.cpp new file mode 100644 index 000000000..10bb95a7a --- /dev/null +++ b/src/libavoid/geomtypes.cpp @@ -0,0 +1,548 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2004-2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + + +#include <cmath> +#include <cfloat> +#include <cstdlib> + +#include "libavoid/geomtypes.h" +#include "libavoid/shape.h" +#include "libavoid/router.h" +#include "libavoid/assertions.h" + + +namespace Avoid +{ + + +Point::Point() : + id(0), + vn(kUnassignedVertexNumber) +{ +} + + +Point::Point(const double xv, const double yv) : + x(xv), + y(yv), + id(0), + vn(kUnassignedVertexNumber) +{ +} + + +bool Point::operator==(const Point& rhs) const +{ + if ((x == rhs.x) && (y == rhs.y)) + { + return true; + } + return false; +} + + +bool Point::operator!=(const Point& rhs) const +{ + if ((x != rhs.x) || (y != rhs.y)) + { + return true; + } + return false; +} + + +// Just defined to allow std::set<Point>. Not particularly meaningful! +bool Point::operator<(const Point& rhs) const +{ + if (x == rhs.x) + { + return (y < rhs.y); + } + return (x < rhs.x); +} + + +double& Point::operator[](const unsigned int dimension) +{ + COLA_ASSERT((dimension == 0) || (dimension == 1)); + return ((dimension == 0) ? x : y); +} + + +const double& Point::operator[](const unsigned int dimension) const +{ + COLA_ASSERT((dimension == 0) || (dimension == 1)); + return ((dimension == 0) ? x : y); +} + + +ReferencingPolygon::ReferencingPolygon(const Polygon& poly, const Router *router) + : PolygonInterface(), + _id(poly._id), + ps(poly.size()) +{ + COLA_ASSERT(router != NULL); + for (size_t i = 0; i < poly.size(); ++i) + { + const Polygon *polyPtr = NULL; + for (ShapeRefList::const_iterator sh = router->shapeRefs.begin(); + sh != router->shapeRefs.end(); ++sh) + { + if ((*sh)->id() == poly.ps[i].id) + { + const Polygon& poly = (*sh)->polygon(); + polyPtr = &poly; + break; + } + } + COLA_ASSERT(polyPtr != NULL); + ps[i] = std::make_pair(polyPtr, poly.ps[i].vn); + } +} + + +ReferencingPolygon::ReferencingPolygon() + : PolygonInterface() +{ + clear(); +} + + +void ReferencingPolygon::clear(void) +{ + ps.clear(); +} + + +bool ReferencingPolygon::empty(void) const +{ + return ps.empty(); +} + + +size_t ReferencingPolygon::size(void) const +{ + return ps.size(); +} + + +int ReferencingPolygon::id(void) const +{ + return _id; +} + + +const Point& ReferencingPolygon::at(size_t index) const +{ + COLA_ASSERT(index < size()); + const Polygon& poly = *(ps[index].first); + unsigned short poly_index = ps[index].second; + COLA_ASSERT(poly_index < poly.size()); + + return poly.ps[poly_index]; +} + + +void PolygonInterface::getBoundingRect(double *minX, double *minY, + double *maxX, double *maxY) const +{ + double progressiveMinX = DBL_MAX; + double progressiveMinY = DBL_MAX; + double progressiveMaxX = -DBL_MAX; + double progressiveMaxY = -DBL_MAX; + + for (size_t i = 0; i < size(); ++i) + { + progressiveMinX = std::min(progressiveMinX, at(i).x); + progressiveMinY = std::min(progressiveMinY, at(i).y); + progressiveMaxX = std::max(progressiveMaxX, at(i).x); + progressiveMaxY = std::max(progressiveMaxY, at(i).y); + } + + if (minX) + { + *minX = progressiveMinX; + } + if (maxX) + { + *maxX = progressiveMaxX; + } + if (minY) + { + *minY = progressiveMinY; + } + if (maxY) + { + *maxY = progressiveMaxY; + } +} + + +Polygon::Polygon() + : PolygonInterface() +{ + clear(); +} + + +Polygon::Polygon(const int pn) + : PolygonInterface(), + ps(pn) +{ +} + + +Polygon::Polygon(const PolygonInterface& poly) + : PolygonInterface(), + _id(poly.id()), + ps(poly.size()) +{ + for (size_t i = 0; i < poly.size(); ++i) + { + ps[i] = poly.at(i); + } +} + + +void Polygon::clear(void) +{ + ps.clear(); + ts.clear(); +} + + +bool Polygon::empty(void) const +{ + return ps.empty(); +} + + +size_t Polygon::size(void) const +{ + return ps.size(); +} + + +int Polygon::id(void) const +{ + return _id; +} + + +const Point& Polygon::at(size_t index) const +{ + COLA_ASSERT(index < size()); + + return ps[index]; +} + + +static const unsigned int SHORTEN_NONE = 0; +static const unsigned int SHORTEN_START = 1; +static const unsigned int SHORTEN_END = 2; +static const unsigned int SHORTEN_BOTH = SHORTEN_START | SHORTEN_END; + +// shorten_line(): +// Given the two endpoints of a line segment, this function adjusts the +// endpoints of the line to shorten the line by shorten_length at either +// or both ends. +// +static void shorten_line(double& x1, double& y1, double& x2, double& y2, + const unsigned int mode, const double shorten_length) +{ + if (mode == SHORTEN_NONE) + { + return; + } + + double rise = y1 - y2; + double run = x1 - x2; + double disty = fabs(rise); + double distx = fabs(run); + + // Handle case where shorten length is greater than the length of the + // line segment. + if ((mode == SHORTEN_BOTH) && + (((distx > disty) && ((shorten_length * 2) > distx)) || + ((disty >= distx) && ((shorten_length * 2) > disty)))) + { + x1 = x2 = x1 - (run / 2); + y1 = y2 = y1 - (rise / 2); + return; + } + else if ((mode == SHORTEN_START) && + (((distx > disty) && (shorten_length > distx)) || + ((disty >= distx) && (shorten_length > disty)))) + { + x1 = x2; + y1 = y2; + return; + } + else if ((mode == SHORTEN_END) && + (((distx > disty) && (shorten_length > distx)) || + ((disty >= distx) && (shorten_length > disty)))) + { + x2 = x1; + y2 = y1; + return; + } + + // Handle orthogonal line segments. + if (x1 == x2) + { + // Vertical + int sign = (y1 < y2) ? 1: -1; + + if (mode & SHORTEN_START) + { + y1 += (sign * shorten_length); + } + if (mode & SHORTEN_END) + { + y2 -= (sign * shorten_length); + } + return; + } + else if (y1 == y2) + { + // Horizontal + int sign = (x1 < x2) ? 1: -1; + + if (mode & SHORTEN_START) + { + x1 += (sign * shorten_length); + } + if (mode & SHORTEN_END) + { + x2 -= (sign * shorten_length); + } + return; + } + + int xpos = (x1 < x2) ? -1 : 1; + int ypos = (y1 < y2) ? -1 : 1; + + double tangent = rise / run; + + if (mode & SHORTEN_END) + { + if (disty > distx) + { + y2 += shorten_length * ypos; + x2 += shorten_length * ypos * (1 / tangent); + } + else if (disty < distx) + { + y2 += shorten_length * xpos * tangent; + x2 += shorten_length * xpos; + } + } + + if (mode & SHORTEN_START) + { + if (disty > distx) + { + y1 -= shorten_length * ypos; + x1 -= shorten_length * ypos * (1 / tangent); + } + else if (disty < distx) + { + y1 -= shorten_length * xpos * tangent; + x1 -= shorten_length * xpos; + } + } +} + + +void Polygon::translate(const double xDist, const double yDist) +{ + for (size_t i = 0; i < size(); ++i) + { + ps[i].x += xDist; + ps[i].y += yDist; + } +} + + +Polygon Polygon::simplify(void) const +{ + Polygon simplified = *this; + std::vector<Point>::iterator it = simplified.ps.begin(); + if (it != simplified.ps.end()) ++it; + + // Combine collinear line segments into single segments: + for (size_t j = 2; j < simplified.size(); ) + { + if (vecDir(simplified.ps[j - 2], simplified.ps[j - 1], + simplified.ps[j]) == 0) + { + // These three points make up two collinear segments, so just + // compine them into a single segment. + it = simplified.ps.erase(it); + } + else + { + ++j; + ++it; + } + } + + return simplified; +} + + +#define mid(a, b) ((a < b) ? a + ((b - a) / 2) : b + ((a - b) / 2)) + + +// curvedPolyline(): +// Returns a curved approximation of this multi-segment PolyLine, with +// the corners replaced by smooth Bezier curves. curve_amount describes +// how large to make the curves. +// The ts value for each point in the returned Polygon describes the +// drawing operation: 'M' (move) marks the first point, a line segment +// is marked with an 'L' and three 'C's (along with the previous point) +// describe the control points of a Bezier curve. +// +Polygon Polygon::curvedPolyline(const double curve_amount, + const bool closed) const +{ + Polygon simplified = this->simplify(); + + Polygon curved; + size_t num_of_points = size(); + if (num_of_points <= 2) + { + // There is only a single segment, do nothing. + curved = *this; + curved.ts.push_back('M'); + curved.ts.push_back('L'); + return curved; + } + + // Build the curved polyline: + curved._id = _id; + double last_x = 0; + double last_y = 0; + if (closed) + { + double x1 = simplified.ps[0].x; + double y1 = simplified.ps[0].y; + double x2 = simplified.ps[1].x; + double y2 = simplified.ps[1].y; + shorten_line(x1, y1, x2, y2, SHORTEN_START, curve_amount); + curved.ps.push_back(Point(x1, y1)); + curved.ts.push_back('M'); + } + else + { + curved.ps.push_back(ps[0]); + curved.ts.push_back('M'); + } + + size_t simpSize = simplified.size(); + size_t finish = (closed) ? simpSize + 2 : simpSize; + for (size_t j = 1; j < finish; ++j) + { + double x1 = simplified.ps[(simpSize + j - 1) % simpSize].x; + double y1 = simplified.ps[(simpSize + j - 1) % simpSize].y; + double x2 = simplified.ps[j % simpSize].x; + double y2 = simplified.ps[j % simpSize].y; + + double old_x = x1; + double old_y = y1; + + unsigned int mode = SHORTEN_BOTH; + if (!closed) + { + if (j == 1) + { + mode = SHORTEN_END; + } + else if (j == (size() - 1)) + { + mode = SHORTEN_START; + } + } + shorten_line(x1, y1, x2, y2, mode, curve_amount); + + if (j > 1) + { + curved.ts.insert(curved.ts.end(), 3, 'C'); + curved.ps.push_back(Point(mid(last_x, old_x), mid(last_y, old_y))); + curved.ps.push_back(Point(mid(x1, old_x), mid(y1, old_y))); + curved.ps.push_back(Point(x1, y1)); + } + if (closed && (j == (finish - 1))) + { + // Close the path. + curved.ts.push_back('Z'); + curved.ps.push_back(Point(x1, y1)); + break; + } + curved.ts.push_back('L'); + curved.ps.push_back(Point(x2, y2)); + + last_x = x2; + last_y = y2; + } + + return curved; +} + + +Rectangle::Rectangle(const Point& topLeft, const Point& bottomRight) + : Polygon(4) +{ + double xMin = std::min(topLeft.x, bottomRight.x); + double xMax = std::max(topLeft.x, bottomRight.x); + double yMin = std::min(topLeft.y, bottomRight.y); + double yMax = std::max(topLeft.y, bottomRight.y); + + ps[0] = Point(xMax, yMin); + ps[1] = Point(xMax, yMax); + ps[2] = Point(xMin, yMax); + ps[3] = Point(xMin, yMin); +} + + +Rectangle::Rectangle(const Point& centre, const double width, + const double height) +{ + double halfWidth = width / 2.0; + double halfHeight = height / 2.0; + double xMin = centre.x - halfWidth; + double xMax = centre.x + halfWidth; + double yMin = centre.y - halfHeight; + double yMax = centre.y + halfHeight; + + ps[0] = Point(xMax, yMin); + ps[1] = Point(xMax, yMax); + ps[2] = Point(xMin, yMax); + ps[3] = Point(xMin, yMin); +} + + +} + diff --git a/src/libavoid/geomtypes.h b/src/libavoid/geomtypes.h index dd9d26f2f..ced53e6b0 100644 --- a/src/libavoid/geomtypes.h +++ b/src/libavoid/geomtypes.h @@ -2,70 +2,314 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ +//! @file geomtypes.h +//! @brief Contains the interface for various geometry types and classes. + #ifndef AVOID_GEOMTYPES_H #define AVOID_GEOMTYPES_H +#include <vector> +#include <utility> + namespace Avoid { - + +//! @brief The Point class defines a point in the plane. +//! +//! Points consist of an x and y value. They may also have an ID and vertex +//! number associated with them. +//! class Point { public: + //! @brief Default constructor. + //! Point(); + //! @brief Standard constructor. + //! + //! @param[in] xv The x position of the point. + //! @param[in] yv The y position of the point. + //! Point(const double xv, const double yv); + + //! @brief Comparison operator. Returns true if at same position. + //! + //! @param[in] rhs The point to compare with this one. + //! @return The result of the comparison. + //! bool operator==(const Point& rhs) const; + //! @brief Comparison operator. Returns true if at different positions. + //! + //! @param[in] rhs The point to compare with this one. + //! @return The result of the comparison. + //! bool operator!=(const Point& rhs) const; + //! @brief Comparison operator. Returns true if less-then rhs point. + //! + //! @note This operator is not particularly useful, but is defined + //! to allow std::set<Point>. + //! + //! @param[in] rhs The point to compare with this one. + //! @return The result of the comparison. + //! + bool operator<(const Point& rhs) const; + //! @brief Returns the x or y value of the point, given the dimension. + //! + //! @param[in] dimension The dimension: 0 for x, 1 for y. + //! @return The component of the point in that dimension. + double& operator[](const unsigned int dimension); + const double& operator[](const unsigned int dimension) const; + + //! The x position. double x; + //! The y position. double y; - int id; + //! The ID associated with this point. + unsigned int id; + //! The vertex number associated with this point. + unsigned short vn; }; +//! Constant value representing an unassigned vertex number. +//! +static const unsigned short kUnassignedVertexNumber = 8; + + +//! @brief A vector, represented by the Point class. +//! typedef Point Vector; -typedef struct +//! @brief A common interface used by the Polygon classes. +//! +class PolygonInterface { - int id; - Point *ps; - int pn; -} Polygn; - -typedef Polygn PolyLine; + public: + //! @brief Constructor. + PolygonInterface() { } + //! @brief Destructor. + virtual ~PolygonInterface() { } + //! @brief Resets this to the empty polygon. + virtual void clear(void) = 0; + //! @brief Returns true if this polygon is empty. + virtual bool empty(void) const = 0; + //! @brief Returns the number of points in this polygon. + virtual size_t size(void) const = 0; + //! @brief Returns the ID value associated with this polygon. + virtual int id(void) const = 0; + //! @brief Returns a specific point in the polygon. + //! @param[in] index The array index of the point to be returned. + virtual const Point& at(size_t index) const = 0; + //! @brief Returns the bounding rectangle that contains this polygon. + //! + //! If a NULL pointer is passed for any of the arguments, then that + //! value is ignored and not returned. + //! + //! @param[out] minX The left hand side of the bounding box. + //! @param[out] minY The top of the bounding box. + //! @param[out] maxX The right hand side of the bounding box. + //! @param[out] maxY The bottom of the bounding box. + void getBoundingRect(double *minX, double *minY, + double *maxX, double *maxY) const; +}; -typedef struct +//! @brief A line between two points. +//! +class Edge { - Point a; - Point b; -} Edge; + public: + //! The first point. + Point a; + //! The second point. + Point b; +}; + +//! @brief A bounding box, represented with an Edge between top-left and +//! bottom-right corners. +//! typedef Edge BBox; +class Router; +class ReferencingPolygon; + + +//! @brief A dynamic Polygon, to which points can be easily added and removed. +//! +//! @note The Rectangle class can be used as an easy way of constructing a +//! square or rectangular polygon. +//! +class Polygon : public PolygonInterface +{ + public: + //! @brief Constructs an empty polygon (with zero points). + Polygon(); + //! @brief Constructs a new polygon with n points. + //! + //! A rectangle would be comprised of four point. An n segment + //! PolyLine (represented as a Polygon) would be comprised of n+1 + //! points. Whether a particular Polygon is closed or not, depends + //! on whether it is a Polygon or Polyline. Shape polygons are always + //! considered to be closed, meaning the last point joins back to the + //! first point. + //! + //! @param[in] n Number of points in the polygon. + //! + Polygon(const int n); + //! @brief Constructs a new polygon from an existing Polygon. + //! + //! @param[in] poly An existing polygon to copy the new polygon from. + //! + Polygon(const PolygonInterface& poly); + //! @brief Resets this to the empty polygon. + void clear(void); + //! @brief Returns true if this polygon is empty. + bool empty(void) const; + //! @brief Returns the number of points in this polygon. + size_t size(void) const; + //! @brief Returns the ID value associated with this polygon. + int id(void) const; + //! @brief Returns a specific point in the polygon. + //! @param[in] index The array index of the point to be returned. + const Point& at(size_t index) const; + //! @brief Returns a simplified Polyline, where all collinear line + //! segments have been collapsed down into single line + //! segments. + //! + //! @return A new polyline with a simplified representation. + //! + Polygon simplify(void) const; + //! @brief Returns a curved approximation of this multi-segment + //! PolyLine, with the corners replaced by smooth Bezier + //! curves. + //! + //! This function does not do any further obstacle avoidance with the + //! curves produced. Hence, you would usually specify a curve_amount + //! in similar size to the space buffer around obstacles in the scene. + //! This way the curves will cut the corners around shapes but still + //! run within this buffer space. + //! + //! @param curve_amount Describes the distance along the end of each + //! line segment to turn into a curve. + //! @param closed Describes whether the Polygon should be + //! treated as closed. Defaults to false. + //! @return A new polyline (polygon) representing the curved path. + //! Its points represent endpoints of line segments and + //! Bezier spline control points. The Polygon::ts vector for + //! this returned polygon is populated with a character for + //! each point describing its type. + //! @sa ts + Polygon curvedPolyline(const double curve_amount, + const bool closed = false) const; + //! @brief Translates the polygon position by a relative amount. + //! + //! @param[in] xDist Distance to move polygon in the x dimension. + //! @param[in] yDist Distance to move polygon in the y dimension. + void translate(const double xDist, const double yDist); + + //! @brief An ID for the polygon. + int _id; + //! @brief A vector of the points that make up the Polygon. + std::vector<Point> ps; + //! @brief If used, denotes whether the corresponding point in ps is + //! a move-to operation or a Bezier curve-to. + //! + //! Each character describes the drawing operation for the + //! corresponding point in the ps vector. Possible values are: + //! - 'M': A moveto operation, marks the first point; + //! - 'L': A lineto operation, is a line from the previous point to + //! the current point; or + //! - 'C': A curveto operation, three consecutive 'C' points + //! (along with the previous point) describe the control points + //! of a Bezier curve. + //! - 'Z': Closes the path (used for cluster boundaries). + //! + //! @note This vector will currently only be populated for polygons + //! returned by curvedPolyline(). + std::vector<char> ts; +}; + + +//! @brief A multi-segment line, represented with the Polygon class. +//! +typedef Polygon PolyLine; + + +//! @brief A Polygon which just references its points from other Polygons. +//! +//! This type of Polygon is used to accurately represent cluster boundaries +//! made up from the corner points of shapes. +//! +class ReferencingPolygon : public PolygonInterface +{ + public: + ReferencingPolygon(); + ReferencingPolygon(const Polygon& poly, const Router *router); + void clear(void); + bool empty(void) const; + size_t size(void) const; + int id(void) const; + const Point& at(size_t index) const; + + int _id; + std::vector<std::pair<const Polygon *, unsigned short> > ps; +}; + + +//! @brief A Rectangle, a simpler way to define the polygon for square or +//! rectangular shapes. +//! +class Rectangle : public Polygon +{ + public: + //! @brief Constructs a rectangular polygon given two opposing + //! corner points. + //! + //! @param[in] topLeft The first corner point of the rectangle. + //! @param[in] bottomRight The opposing corner point of the rectangle. + //! + Rectangle(const Point& topLeft, const Point& bottomRight); + + //! @brief Constructs a rectangular polygon given the centre, width + //! and height. + //! + //! @param[in] centre The centre of the rectangle, specified as + //! a point. + //! @param[in] width The width of the rectangle. + //! @param[in] height The height of the rectangle. + //! + Rectangle(const Point& centre, const double width, const double height); +}; + + } #endif diff --git a/src/libavoid/graph.cpp b/src/libavoid/graph.cpp index 1970212df..728f8c085 100644 --- a/src/libavoid/graph.cpp +++ b/src/libavoid/graph.cpp @@ -2,56 +2,61 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + +#include <cmath> + #include "libavoid/debug.h" #include "libavoid/graph.h" #include "libavoid/connector.h" #include "libavoid/geometry.h" -#include "libavoid/polyutil.h" #include "libavoid/timer.h" #include "libavoid/vertices.h" #include "libavoid/router.h" +#include "libavoid/assertions.h" -#include <math.h> using std::pair; namespace Avoid { -EdgeInf::EdgeInf(VertInf *v1, VertInf *v2) - : lstPrev(NULL) - , lstNext(NULL) - , _blocker(0) - , _router(NULL) - , _added(false) - , _visible(false) - , _v1(v1) - , _v2(v2) - , _dist(-1) +EdgeInf::EdgeInf(VertInf *v1, VertInf *v2, const bool orthogonal) + : lstPrev(NULL), + lstNext(NULL), + _blocker(0), + _router(NULL), + _added(false), + _visible(false), + _orthogonal(orthogonal), + _v1(v1), + _v2(v2), + _dist(-1) { // Not passed NULL values. - assert(v1 && v2); + COLA_ASSERT(v1 && v2); // We are in the same instance - assert(_v1->_router == _v2->_router); + COLA_ASSERT(_v1->_router == _v2->_router); _router = _v1->_router; _conns.clear(); @@ -67,25 +72,142 @@ EdgeInf::~EdgeInf() } +// Gives an order value between 0 and 3 for the point c, given the last +// segment was from a to b. Returns the following value: +// 0 : Point c is directly backwards from point b. +// 1 : Point c is a left-hand 90 degree turn. +// 2 : Point c is a right-hand 90 degree turn. +// 3 : Point c is straight ahead (collinear). +// +static inline int orthogTurnOrder(const Point& a, const Point& b, + const Point& c) +{ + // We should only be calling this with orthogonal points, + COLA_ASSERT((c.x == b.x) || (c.y == b.y)); + COLA_ASSERT((a.x == b.x) || (a.y == b.y)); + + int direction = vecDir(a, b, c); + + if (direction > 0) + { + // Counterclockwise := left + return 1; + } + else if (direction < 0) + { + // Clockwise := right + return 2; + } + + if (b.x == c.x) + { + if ( ((a.y < b.y) && (c.y < b.y)) || + ((a.y > b.y) && (c.y > b.y)) ) + { + // Behind. + return 0; + } + } + else + { + if ( ((a.x < b.x) && (c.x < b.x)) || + ((a.x > b.x) && (c.x > b.x)) ) + { + // Behind. + return 0; + } + } + + // Ahead. + return 3; +} + + +// Returns a less than operation for a set exploration order for orthogonal +// searching. Forward, then left, then right. Or if there is no previous +// point, then the order is north, east, south, then west. +// Note: This method assumes the two Edges that share a common point. +bool EdgeInf::rotationLessThan(const VertInf *lastV, const EdgeInf *rhs) const +{ + if ((_v1 == rhs->_v1) && (_v2 == rhs->_v2)) + { + // Effectively the same visibility edge, so they are equal. + return false; + } + VertInf *lhsV = NULL, *rhsV = NULL, *commonV = NULL; + + // Determine common Point and the comparison point on the left- and + // the right-hand-side. + if (_v1 == rhs->_v1) + { + commonV = _v1; + lhsV = _v2; + rhsV = rhs->_v2; + } + else if (_v1 == rhs->_v2) + { + commonV = _v1; + lhsV = _v2; + rhsV = rhs->_v1; + } + else if (_v2 == rhs->_v1) + { + commonV = _v2; + lhsV = _v1; + rhsV = rhs->_v2; + } + else if (_v2 == rhs->_v2) + { + commonV = _v2; + lhsV = _v1; + rhsV = rhs->_v1; + } + + const Point& lhsPt = lhsV->point; + const Point& rhsPt = rhsV->point; + const Point& commonPt = commonV->point; + + // If no lastPt, use one directly to the left; + Point lastPt = (lastV) ? lastV->point : Point(commonPt.x - 10, commonPt.y); + + int lhsVal = orthogTurnOrder(lastPt, commonPt, lhsPt); + int rhsVal = orthogTurnOrder(lastPt, commonPt, rhsPt); + + return lhsVal < rhsVal; +} + + void EdgeInf::makeActive(void) { - assert(_added == false); + COLA_ASSERT(_added == false); - if (_visible) + if (_orthogonal) { - _router->visGraph.addEdge(this); - _pos1 = _v1->visList.insert(_v1->visList.begin(), this); - _v1->visListSize++; - _pos2 = _v2->visList.insert(_v2->visList.begin(), this); - _v2->visListSize++; + COLA_ASSERT(_visible); + _router->visOrthogGraph.addEdge(this); + _pos1 = _v1->orthogVisList.insert(_v1->orthogVisList.begin(), this); + _v1->orthogVisListSize++; + _pos2 = _v2->orthogVisList.insert(_v2->orthogVisList.begin(), this); + _v2->orthogVisListSize++; } - else // if (invisible) + else { - _router->invisGraph.addEdge(this); - _pos1 = _v1->invisList.insert(_v1->invisList.begin(), this); - _v1->invisListSize++; - _pos2 = _v2->invisList.insert(_v2->invisList.begin(), this); - _v2->invisListSize++; + if (_visible) + { + _router->visGraph.addEdge(this); + _pos1 = _v1->visList.insert(_v1->visList.begin(), this); + _v1->visListSize++; + _pos2 = _v2->visList.insert(_v2->visList.begin(), this); + _v2->visListSize++; + } + else // if (invisible) + { + _router->invisGraph.addEdge(this); + _pos1 = _v1->invisList.insert(_v1->invisList.begin(), this); + _v1->invisListSize++; + _pos2 = _v2->invisList.insert(_v2->invisList.begin(), this); + _v2->invisListSize++; + } } _added = true; } @@ -93,23 +215,35 @@ void EdgeInf::makeActive(void) void EdgeInf::makeInactive(void) { - assert(_added == true); + COLA_ASSERT(_added == true); - if (_visible) + if (_orthogonal) { - _router->visGraph.removeEdge(this); - _v1->visList.erase(_pos1); - _v1->visListSize--; - _v2->visList.erase(_pos2); - _v2->visListSize--; + COLA_ASSERT(_visible); + _router->visOrthogGraph.removeEdge(this); + _v1->orthogVisList.erase(_pos1); + _v1->orthogVisListSize--; + _v2->orthogVisList.erase(_pos2); + _v2->orthogVisListSize--; } - else // if (invisible) + else { - _router->invisGraph.removeEdge(this); - _v1->invisList.erase(_pos1); - _v1->invisListSize--; - _v2->invisList.erase(_pos2); - _v2->invisListSize--; + if (_visible) + { + _router->visGraph.removeEdge(this); + _v1->visList.erase(_pos1); + _v1->visListSize--; + _v2->visList.erase(_pos2); + _v2->visListSize--; + } + else // if (invisible) + { + _router->invisGraph.removeEdge(this); + _v1->invisList.erase(_pos1); + _v1->invisListSize--; + _v2->invisList.erase(_pos2); + _v2->invisListSize--; + } } _blocker = 0; _conns.clear(); @@ -119,11 +253,12 @@ void EdgeInf::makeInactive(void) void EdgeInf::setDist(double dist) { - //assert(dist != 0); + //COLA_ASSERT(dist != 0); if (_added && !_visible) { makeInactive(); + COLA_ASSERT(!_added); } if (!_added) { @@ -135,6 +270,12 @@ void EdgeInf::setDist(double dist) } +bool EdgeInf::added(void) +{ + return _added; +} + + void EdgeInf::alertConns(void) { FlagList::iterator finish = _conns.end(); @@ -161,11 +302,12 @@ void EdgeInf::addCycleBlocker(void) void EdgeInf::addBlocker(int b) { - assert(_router->InvisibilityGrph); + COLA_ASSERT(_router->InvisibilityGrph); if (_added && _visible) { makeInactive(); + COLA_ASSERT(!_added); } if (!_added) { @@ -232,8 +374,11 @@ void EdgeInf::checkVis(void) cone1 = inValidRegion(_router->IgnoreRegions, i->shPrev->point, iPoint, i->shNext->point, jPoint); } - else + else if (_router->IgnoreRegions == false) { + // If Ignoring regions then this case is already caught by + // the invalid regions, so only check it when not ignoring + // regions. ShapeSet& ss = _router->contains[iID]; if ((jID.isShape) && (ss.find(jID.objID) != ss.end())) @@ -253,8 +398,11 @@ void EdgeInf::checkVis(void) cone2 = inValidRegion(_router->IgnoreRegions, j->shPrev->point, jPoint, j->shNext->point, iPoint); } - else + else if (_router->IgnoreRegions == false) { + // If Ignoring regions then this case is already caught by + // the invalid regions, so only check it when not ignoring + // regions. ShapeSet& ss = _router->contains[jID]; if ((iID.isShape) && (ss.find(iID.objID) != ss.end())) @@ -274,7 +422,7 @@ void EdgeInf::checkVis(void) db_printf("\tSetting visibility edge... \n\t\t"); db_print(); - double d = dist(iPoint, jPoint); + double d = euclideanDist(iPoint, jPoint); setDist(d); @@ -316,26 +464,39 @@ int EdgeInf::firstBlocker(void) } VertInf *last = _router->vertices.end(); + unsigned int lastId = 0; + bool seenIntersectionAtEndpoint = false; for (VertInf *k = _router->vertices.shapesBegin(); k != last; ) { VertID kID = k->id; - if ((ss.find(kID.objID) != ss.end())) + if (k->id == dummyOrthogID) + { + // Don't include orthogonal dummy vertices. + k = k->lstNext; + continue; + } + if (kID.objID != lastId) { - unsigned int shapeID = kID.objID; - db_printf("Endpoint is inside shape %u so ignore shape edges.\n", - kID.objID); - // One of the endpoints is inside this shape so ignore it. - while ((k != last) && (k->id.objID == shapeID)) + if ((ss.find(kID.objID) != ss.end())) { - // And skip the other vertices from this shape. - k = k->lstNext; + unsigned int shapeID = kID.objID; + db_printf("Endpoint is inside shape %u so ignore shape " + "edges.\n", kID.objID); + // One of the endpoints is inside this shape so ignore it. + while ((k != last) && (k->id.objID == shapeID)) + { + // And skip the other vertices from this shape. + k = k->lstNext; + } + continue; } - continue; + seenIntersectionAtEndpoint = false; + lastId = kID.objID; } Point& kPoint = k->point; Point& kPrevPoint = k->shPrev->point; - - if (segmentIntersect(pti, ptj, kPrevPoint, kPoint)) + if (segmentShapeIntersect(pti, ptj, kPrevPoint, kPoint, + seenIntersectionAtEndpoint)) { ss.clear(); return kID.objID; @@ -358,9 +519,17 @@ bool EdgeInf::isBetween(VertInf *i, VertInf *j) } + // Returns true if this edge is a vertical or horizontal line segment. +bool EdgeInf::isOrthogonal(void) const +{ + return ((_v1->point.x == _v2->point.x) || + (_v1->point.y == _v2->point.y)); +} + + VertInf *EdgeInf::otherVert(VertInf *vert) { - assert((vert == _v1) || (vert == _v2)); + COLA_ASSERT((vert == _v1) || (vert == _v2)); if (vert == _v1) { @@ -372,12 +541,17 @@ VertInf *EdgeInf::otherVert(VertInf *vert) EdgeInf *EdgeInf::checkEdgeVisibility(VertInf *i, VertInf *j, bool knownNew) { + // This is for polyline routing, so check we're not + // considering orthogonal vertices. + COLA_ASSERT(i->id != dummyOrthogID); + COLA_ASSERT(j->id != dummyOrthogID); + Router *router = i->_router; EdgeInf *edge = NULL; if (knownNew) { - assert(existingEdge(i, j) == NULL); + COLA_ASSERT(existingEdge(i, j) == NULL); edge = new EdgeInf(i, j); } else @@ -399,22 +573,17 @@ EdgeInf *EdgeInf::checkEdgeVisibility(VertInf *i, VertInf *j, bool knownNew) } + // XXX: This function is ineffecient, and shouldn't even really be + // required. EdgeInf *EdgeInf::existingEdge(VertInf *i, VertInf *j) { VertInf *selected = NULL; - if (i->visListSize <= j->visListSize) - { - selected = i; - } - else - { - selected = j; - } - + // Look through poly-line visibility edges. + selected = (i->visListSize <= j->visListSize) ? i : j; EdgeInfList& visList = selected->visList; - EdgeInfList::iterator finish = visList.end(); - for (EdgeInfList::iterator edge = visList.begin(); edge != finish; + EdgeInfList::const_iterator finish = visList.end(); + for (EdgeInfList::const_iterator edge = visList.begin(); edge != finish; ++edge) { if ((*edge)->isBetween(i, j)) @@ -423,18 +592,24 @@ EdgeInf *EdgeInf::existingEdge(VertInf *i, VertInf *j) } } - if (i->invisListSize <= j->invisListSize) - { - selected = i; - } - else + // Look through orthogonal visbility edges. + selected = (i->orthogVisListSize <= j->orthogVisListSize) ? i : j; + EdgeInfList& orthogVisList = selected->orthogVisList; + finish = orthogVisList.end(); + for (EdgeInfList::const_iterator edge = orthogVisList.begin(); + edge != finish; ++edge) { - selected = j; + if ((*edge)->isBetween(i, j)) + { + return (*edge); + } } + // Look through poly-line invisbility edges. + selected = (i->invisListSize <= j->invisListSize) ? i : j; EdgeInfList& invisList = selected->invisList; finish = invisList.end(); - for (EdgeInfList::iterator edge = invisList.begin(); edge != finish; + for (EdgeInfList::const_iterator edge = invisList.begin(); edge != finish; ++edge) { if ((*edge)->isBetween(i, j)) @@ -450,19 +625,45 @@ EdgeInf *EdgeInf::existingEdge(VertInf *i, VertInf *j) //=========================================================================== -EdgeList::EdgeList() - : _firstEdge(NULL) - , _lastEdge(NULL) - , _count(0) +EdgeList::EdgeList(bool orthogonal) + : _orthogonal(orthogonal), + _firstEdge(NULL), + _lastEdge(NULL), + _count(0) +{ +} + + +EdgeList::~EdgeList() +{ + clear(); +} + + +void EdgeList::clear(void) +{ + while (_firstEdge) + { + delete _firstEdge; + } + COLA_ASSERT(_count == 0); + _lastEdge = NULL; +} + + +int EdgeList::size(void) const { + return _count; } void EdgeList::addEdge(EdgeInf *edge) { + COLA_ASSERT(!_orthogonal || edge->isOrthogonal()); + if (_firstEdge == NULL) { - assert(_lastEdge == NULL); + COLA_ASSERT(_lastEdge == NULL); _lastEdge = edge; _firstEdge = edge; @@ -472,7 +673,7 @@ void EdgeList::addEdge(EdgeInf *edge) } else { - assert(_lastEdge != NULL); + COLA_ASSERT(_lastEdge != NULL); _lastEdge->lstNext = edge; edge->lstPrev = _lastEdge; diff --git a/src/libavoid/graph.h b/src/libavoid/graph.h index 05f03a988..db776b80b 100644 --- a/src/libavoid/graph.h +++ b/src/libavoid/graph.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef AVOID_GRAPH_H #define AVOID_GRAPH_H @@ -31,6 +34,7 @@ namespace Avoid { + class ConnRef; class Router; @@ -42,8 +46,8 @@ typedef std::list<bool *> FlagList; class EdgeInf { public: - EdgeInf(VertInf *v1, VertInf *v2); - virtual ~EdgeInf(); + EdgeInf(VertInf *v1, VertInf *v2, const bool orthogonal = false); + ~EdgeInf(); inline double getDist(void) { return _dist; @@ -53,6 +57,9 @@ class EdgeInf void addConn(bool *flag); void addCycleBlocker(void); void addBlocker(int b); + bool added(void); + bool isOrthogonal(void) const; + bool rotationLessThan(const VertInf* last, const EdgeInf *rhs) const; std::pair<VertID, VertID> ids(void); std::pair<Point, Point> points(void); @@ -70,6 +77,7 @@ class EdgeInf Router *_router; bool _added; bool _visible; + bool _orthogonal; VertInf *_v1; VertInf *_v2; EdgeInfList::iterator _pos1; @@ -87,12 +95,17 @@ class EdgeInf class EdgeList { public: - EdgeList(); - void addEdge(EdgeInf *edge); - void removeEdge(EdgeInf *edge); + friend class EdgeInf; + EdgeList(bool orthogonal = false); + ~EdgeList(); + void clear(void); EdgeInf *begin(void); EdgeInf *end(void); + int size(void) const; private: + void addEdge(EdgeInf *edge); + void removeEdge(EdgeInf *edge); + bool _orthogonal; EdgeInf *_firstEdge; EdgeInf *_lastEdge; unsigned int _count; diff --git a/src/libavoid/libavoid.h b/src/libavoid/libavoid.h index d598c6c74..1d4cd1fdc 100644 --- a/src/libavoid/libavoid.h +++ b/src/libavoid/libavoid.h @@ -2,29 +2,37 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ +//! @file libavoid.h +//! @brief Standard libavoid include file which includes all libavoid +//! header files. + +//! @namespace Avoid +//! @brief The namespace used by code in the libavoid library. + #ifndef AVOID_LIBAVOID_H #define AVOID_LIBAVOID_H #include "libavoid/geomtypes.h" -#include "libavoid/polyutil.h" #include "libavoid/connector.h" #include "libavoid/graph.h" #include "libavoid/debug.h" @@ -32,7 +40,6 @@ #include "libavoid/makepath.h" #include "libavoid/vertices.h" #include "libavoid/visibility.h" -#include "libavoid/static.h" #include "libavoid/router.h" #endif diff --git a/src/libavoid/makefile b/src/libavoid/makefile new file mode 100644 index 000000000..e4f83a52d --- /dev/null +++ b/src/libavoid/makefile @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + + + +OBJEXT = o + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libavoid/all + +clean %.a %.$(OBJEXT): + cd .. && $(MAKE) libavoid/$@ + +.PHONY: all clean + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libavoid/makepath.cpp b/src/libavoid/makepath.cpp index 3a57f8e4e..4e15dbca9 100644 --- a/src/libavoid/makepath.cpp +++ b/src/libavoid/makepath.cpp @@ -2,43 +2,105 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> * - * -------------------------------------------------------------------- - * The dijkstraPath function is based on code published and described - * in "Algorithms in C" (Second Edition), 1990, by Robert Sedgewick. - * -------------------------------------------------------------------- + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + +#include <algorithm> +#include <vector> +#include <climits> +#define _USE_MATH_DEFINES +#include <cmath> + #include "libavoid/vertices.h" #include "libavoid/makepath.h" #include "libavoid/geometry.h" #include "libavoid/connector.h" #include "libavoid/graph.h" #include "libavoid/router.h" -#include <algorithm> -#include <vector> -#include <climits> -#include <limits.h> -#include <math.h> +#include "libavoid/debug.h" +#include "libavoid/assertions.h" +#ifdef ASTAR_DEBUG + #include <SDL_gfxPrimitives.h> +#endif namespace Avoid { +class ANode +{ + public: + VertInf* inf; + double g; // Gone + double h; // Heuristic + double f; // Formula f = g + h + + int prevIndex; // Index into DONE for the previous ANode. + int timeStamp; // Timestamp used to determine explaration order of + // seemingly equal paths during orthogonal routing. + + ANode(VertInf *vinf, int time) + : inf(vinf), + g(0), + h(0), + f(0), + prevIndex(-1), + timeStamp(time) + { + } + ANode() + : inf(NULL), + g(0), + h(0), + f(0), + prevIndex(-1), + timeStamp(-1) + { + } +}; + + +// This returns the opposite result (>) so that when used with stl::make_heap, +// the head node of the heap will be the smallest value, rather than the +// largest. This saves us from having to sort the heap (and then reorder +// it back into a heap) when getting the next node to examine. This way we +// get better complexity -- logarithmic pushs and pops to the heap. +// +bool operator<(const ANode &a, const ANode &b) +{ + if (a.f != b.f) + { + return a.f > b.f; + } + if (a.timeStamp != b.timeStamp) + { + // Tiebreaker, if two paths have equal cost, then choose the one with + // the highest timeStamp. This corresponds to the furthest point + // explored along the straight-line path. When exploring we give the + // directions the following timeStamps; left:1, right:2 and forward:3, + // then we always try to explore forward first. + return a.timeStamp < b.timeStamp; + } + COLA_ASSERT(a.prevIndex != b.prevIndex); + return a.prevIndex > b.prevIndex; +} + static double Dot(const Point& l, const Point& r) { @@ -56,6 +118,13 @@ static double CrossLength(const Point& l, const Point& r) // static double angleBetween(const Point& p1, const Point& p2, const Point& p3) { + if ((p1.x == p2.x && p1.y == p2.y) || (p2.x == p3.x && p2.y == p3.y)) + { + // If two of the points are the same, then we can't say anything + // about the angle between. Treat them as being collinear. + return M_PI; + } + Point v1(p1.x - p2.x, p1.y - p2.y); Point v2(p3.x - p2.x, p3.y - p2.y); @@ -63,21 +132,45 @@ static double angleBetween(const Point& p1, const Point& p2, const Point& p3) } +// Construct a temporary Polygon path given several VertInf's for a connector. +// +static void constructPolygonPath(Polygon& connRoute, VertInf *inf2, + VertInf *inf3, std::vector<ANode>& done, int inf1Index) +{ + int routeSize = 2; + for (int curr = inf1Index; curr >= 0; curr = done[curr].prevIndex) + { + routeSize += 1; + } + connRoute.ps.resize(routeSize); + connRoute.ps[routeSize - 1] = inf3->point; + connRoute.ps[routeSize - 2] = inf2->point; + routeSize -= 3; + for (int curr = inf1Index; curr >= 0; curr = done[curr].prevIndex) + { + connRoute.ps[routeSize] = done[curr].inf->point; + routeSize -= 1; + } +} + + // Given the two points for a new segment of a path (inf2 & inf3) // as well as the distance between these points (dist), as well as // possibly the previous point (inf1) [from inf1--inf2], return a // cost associated with this route. // -double cost(ConnRef *lineRef, const double dist, VertInf *inf1, - VertInf *inf2, VertInf *inf3) +static double cost(ConnRef *lineRef, const double dist, VertInf *inf2, + VertInf *inf3, std::vector<ANode>& done, int inf1Index) { + VertInf *inf1 = (inf1Index >= 0) ? done[inf1Index].inf : NULL; double result = dist; + Polygon connRoute; Router *router = inf2->_router; - if (inf2->pathNext != NULL) + if (inf1 != NULL) { - double& angle_penalty = router->angle_penalty; - double& segmt_penalty = router->segmt_penalty; + const double angle_penalty = router->routingPenalty(anglePenalty); + const double segmt_penalty = router->routingPenalty(segmentPenalty); // This is not the first segment, so there is a bend // between it and the last one in the existing path. @@ -89,29 +182,83 @@ double cost(ConnRef *lineRef, const double dist, VertInf *inf1, double rad = M_PI - angleBetween(p1, p2, p3); - // Make `xval' between 0--10 then take its log so small - // angles are not penalised as much as large ones. - // - double xval = rad * 10 / M_PI; - double yval = xval * log10(xval + 1) / 10.5; - result += (angle_penalty * yval); - //printf("deg from straight: %g\tpenalty: %g\n", - // rad * 180 / M_PI, (angle_penalty * yval)); - - // Don't penalise as an extra segment if there is no turn. - if (rad > 0.0005) + if (rad > 0) + { + // Make `xval' between 0--10 then take its log so small + // angles are not penalised as much as large ones. + // + double xval = rad * 10 / M_PI; + double yval = xval * log10(xval + 1) / 10.5; + result += (angle_penalty * yval); + //db_printf("deg from straight: %g\tpenalty: %g\n", + // rad * 180 / M_PI, (angle_penalty * yval)); + } + + if (rad == M_PI) { + // Needs to double back + result += (2 * segmt_penalty); + } + else if (rad > 0) + { + // Only penalise as an extra segment if the two + // segments are not collinear. result += segmt_penalty; } } } - if (lineRef->doesHateCrossings() && (router->crossing_penalty > 0)) + if (!router->_inCrossingPenaltyReroutingStage) { - Point& a1 = inf2->point; - Point& a2 = inf3->point; + // Return here if we ar not in the postprocessing stage + return result; + } - ConnRefList::iterator curr, finish = router->connRefs.end(); + const double cluster_crossing_penalty = + router->routingPenalty(clusterCrossingPenalty); + // XXX: Clustered routing doesn't yet work with orthogonal connectors. + if (router->ClusteredRouting && !router->clusterRefs.empty() && + (cluster_crossing_penalty > 0) && + (lineRef->routingType() != ConnType_Orthogonal)) + { + if (connRoute.empty()) + { + constructPolygonPath(connRoute, inf2, inf3, done, inf1Index); + } + // There are clusters so do cluster routing. + for (ClusterRefList::const_iterator cl = router->clusterRefs.begin(); + cl != router->clusterRefs.end(); ++cl) + { + ReferencingPolygon& cBoundary = (*cl)->polygon(); + COLA_ASSERT(cBoundary.ps[0] != cBoundary.ps[cBoundary.size() - 1]); + for (size_t j = 0; j < cBoundary.size(); ++j) + { + // Cluster boundary points should correspond to shape + // vertices and hence already be in the list of vertices. + COLA_ASSERT(router->vertices.getVertexByPos(cBoundary.at(j))!=NULL); + } + + bool isConn = false; + Polygon dynamic_c_boundary(cBoundary); + Polygon dynamic_conn_route(connRoute); + const bool finalSegment = (inf3 == lineRef->dst()); + CrossingsInfoPair crossings = countRealCrossings( + dynamic_c_boundary, isConn, dynamic_conn_route, + connRoute.size() - 1, true, finalSegment); + result += (crossings.first * cluster_crossing_penalty); + } + } + + const double shared_path_penalty = + router->routingPenalty(fixedSharedPathPenalty); + if (shared_path_penalty > 0) + { + // Penalises shared paths, except if the connectors shared an endpoint. + if (connRoute.empty()) + { + constructPolygonPath(connRoute, inf2, inf3, done, inf1Index); + } + ConnRefList::const_iterator curr, finish = router->connRefs.end(); for (curr = router->connRefs.begin(); curr != finish; ++curr) { ConnRef *connRef = *curr; @@ -120,306 +267,237 @@ double cost(ConnRef *lineRef, const double dist, VertInf *inf1, { continue; } - Avoid::PolyLine& route2 = connRef->route(); - for (int j = 1; j < route2.pn; ++j) - { - Avoid::Point& b1 = route2.ps[j - 1]; - Avoid::Point& b2 = route2.ps[j]; + const Avoid::PolyLine& route2 = connRef->route(); - if (((a1 == b1) && (a2 == b2)) || - ((a2 == b1) && (a1 == b2))) - { - // Route along same segment: no penalty. We detect - // crossovers when we see the segments diverge. - continue; - } - - if ((a2 == b2) || (a2 == b1) || (b2 == a1)) - { - // Each crossing that is at a vertex in the - // visibility graph gets noticed four times. - // We ignore three of these cases. - // This also catches the case of a shared path, - // but this is one that terminates at a common - // endpoint, so we don't care about it. - continue; - } - - if (a1 == b1) - { - if (j == 1) - { - // common source point. - continue; - } - Avoid::Point& b0 = route2.ps[j - 2]; - // The segments share an endpoint -- a1==b1. - if (a2 == b0) - { - // a2 is not a split, continue. - continue; - } - - // If here, then we know that a2 != b2 - // And a2 and its pair in b are a split. - assert(a2 != b2); - - if (inf2->pathNext == NULL) - { - continue; - } - Avoid::Point& a0 = inf1->point; - - if ((a0 == b0) || (a0 == b2)) - { - //printf("Shared path... "); - bool normal = (a0 == b0) ? true : false; - // Determine direction we have to look through - // the points of connector b. - int dir = normal ? -1 : 1; - - int traceJ = j - 1 + dir; - - int endCornerSide = Avoid::cornerSide( - a0, a1, a2, normal ? b2 : b0); - - - VertInf *traceInf1 = inf2->pathNext; - VertInf *traceInf2 = inf2; - VertInf *traceInf3 = inf3; - while (traceInf1 && - (traceJ >= 0) && (traceJ < route2.pn) && - (traceInf1->point == route2.ps[traceJ])) - { - traceInf3 = traceInf2; - traceInf2 = traceInf1; - traceInf1 = traceInf1->pathNext; - traceJ += dir; - } - - if (!traceInf1 || - (traceJ < 0) || (traceJ >= route2.pn)) - { - //printf("common source or destination.\n"); - // The connectors have a shared path, but it - // comes from a common source point. - // XXX: There might be a better way to - // check this by asking the connectors - // for the IDs of the attached shapes. - continue; - } - - int startCornerSide = Avoid::cornerSide( - traceInf1->point, traceInf2->point, - traceInf3->point, route2.ps[traceJ]); - - if (endCornerSide != startCornerSide) - { - //printf("crosses.\n"); - result += router->crossing_penalty; - } - else - { - //printf("doesn't cross.\n"); - } - } - else - { - // The connectors cross or touch at this point. - //printf("Cross or touch at point... "); - - int side1 = Avoid::cornerSide(a0, a1, a2, b0); - int side2 = Avoid::cornerSide(a0, a1, a2, b2); - - if (side1 != side2) - { - //printf("cross.\n"); - // The connectors cross at this point. - result += router->crossing_penalty; - } - else - { - //printf("touch.\n"); - // The connectors touch at this point. - } - } - continue; - } + bool isConn = true; + Polygon dynamic_route2(route2); + Polygon dynamic_conn_route(connRoute); + CrossingsInfoPair crossings = countRealCrossings( + dynamic_route2, isConn, dynamic_conn_route, + connRoute.size() - 1, false, false, NULL, NULL, + connRef, lineRef); + + if ((crossings.second & CROSSING_SHARES_PATH) && + (crossings.second & CROSSING_SHARES_FIXED_SEGMENT) && + !(crossings.second & CROSSING_SHARES_PATH_AT_END)) + { + // Penalise unecessary shared paths in the middle of + // connectors. + result += shared_path_penalty; + } + } + } - double xc, yc; - int intersectResult = Avoid::segmentIntersectPoint( - a1, a2, b1, b2, &xc, &yc); + const double crossing_penalty = router->routingPenalty(crossingPenalty); + if (lineRef->doesHateCrossings() && (crossing_penalty > 0)) + { + if (connRoute.empty()) + { + constructPolygonPath(connRoute, inf2, inf3, done, inf1Index); + } + ConnRefList::const_iterator curr, finish = router->connRefs.end(); + for (curr = router->connRefs.begin(); curr != finish; ++curr) + { + ConnRef *connRef = *curr; - if (intersectResult == Avoid::DO_INTERSECT) - { - result += router->crossing_penalty; - } + if (connRef->id() == lineRef->id()) + { + continue; } + const Avoid::PolyLine& route2 = connRef->route(); + + bool isConn = true; + Polygon dynamic_route2(route2); + Polygon dynamic_conn_route(connRoute); + CrossingsInfoPair crossings = countRealCrossings( + dynamic_route2, isConn, dynamic_conn_route, + connRoute.size() - 1, true); + result += (crossings.first * crossing_penalty); } } - + return result; } -// Returns the best path from src to tar using the cost function. -// -// The path is worked out via Dijkstra's algorithm, and is encoded via -// pathNext links in each of the VerInfs along the path. -// -// Based on the code of 'matrixpfs'. -// -static void dijkstraPath(ConnRef *lineRef, VertInf *src, VertInf *tar) +static double estimatedCost(ConnRef *lineRef, const Point *last, + const Point& a, const Point& b) { - Router *router = src->_router; - - double unseen = (double) __INT_MAX__; - - // initialize arrays - VertInf *finish = router->vertices.end(); - for (VertInf *t = router->vertices.connsBegin(); t != finish; t = t->lstNext) + if (lineRef->routingType() == ConnType_PolyLine) { - t->pathNext = NULL; - t->pathDist = -unseen; + return euclideanDist(a, b); } - - VertInf *min = src; - while (min != tar) + else // Orthogonal { - VertInf *k = min; - min = NULL; - - k->pathDist *= -1; - if (k->pathDist == unseen) + // XXX: This currently just takes into account the compulsory + // bend but will have to be updated when port direction + // information is available. + int num_penalties = 0; + double xmove = b.x - a.x; + double ymove = b.y - a.y; + if (!last) { - k->pathDist = 0; - } - - EdgeInfList& visList = k->visList; - EdgeInfList::iterator finish = visList.end(); - for (EdgeInfList::iterator edge = visList.begin(); edge != finish; - ++edge) - { - VertInf *t = (*edge)->otherVert(k); - VertID tID = t->id; - - // Only check shape verticies, or endpoints. - if ((t->pathDist < 0) && - ((tID.objID == src->id.objID) || tID.isShape)) + // Just two points. + if ((xmove != 0) && (ymove != 0)) { - double kt_dist = (*edge)->getDist(); - double priority = k->pathDist + - cost(lineRef, kt_dist, k->pathNext, k, t); - - if ((kt_dist != 0) && (t->pathDist < -priority)) - { - t->pathDist = -priority; - t->pathNext = k; - } - if ((min == NULL) || (t->pathDist > min->pathDist)) - { - min = t; - } + num_penalties += 1; } } - EdgeInfList& invisList = k->invisList; - finish = invisList.end(); - for (EdgeInfList::iterator edge = invisList.begin(); edge != finish; - ++edge) + else { - VertInf *t = (*edge)->otherVert(k); - VertID tID = t->id; - - // Only check shape verticies, or endpoints. - if ((t->pathDist < 0) && - ((tID.objID == src->id.objID) || tID.isShape > 0)) + // We have three points, so we know the direction of the + // previous segment. + double rad = M_PI - angleBetween(*last, a, b); + if (rad > (M_PI / 2)) { - if ((min == NULL) || (t->pathDist > min->pathDist)) - { - min = t; - } + // Target point is back in the direction of the first point, + // so at least two bends are required. + num_penalties += 2; + } + else if (rad > 0) + { + // To the side, so at least one bend. + num_penalties += 1; } } + double penalty = num_penalties * + lineRef->router()->routingPenalty(segmentPenalty); + + return manhattanDist(a, b) + penalty; } } -class ANode +class CmpVisEdgeRotation { public: - VertInf* inf; - double g; // Gone - double h; // Heuristic - double f; // Formula f = g + h - VertInf *pp; - - ANode(VertInf *vinf) - : inf(vinf) - , g(0) - , h(0) - , f(0) - , pp(NULL) + CmpVisEdgeRotation(const VertInf* lastPt) + : _lastPt(lastPt) { } - ANode() - : inf(NULL) - , g(0) - , h(0) - , f(0) - , pp(NULL) + bool operator() (const EdgeInf* u, const EdgeInf* v) const { + return u->rotationLessThan(_lastPt, v); } + private: + const VertInf *_lastPt; }; -bool operator<(const ANode &a, const ANode &b) -{ - return a.f < b.f; -} - - -bool operator>(const ANode &a, const ANode &b) -{ - return a.f > b.f; -} - // Returns the best path from src to tar using the cost function. // // The path is worked out using the aStar algorithm, and is encoded via -// pathNext links in each of the VerInfs along the path. +// prevIndex values for each ANode which point back to the previous ANode's +// position in the DONE vector. At completion, this order is written into +// the pathNext links in each of the VerInfs along the path. // // The aStar STL code is based on public domain code available on the // internet. // -static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) +static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar, + VertInf *start) { + bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal); + + double (*dist)(const Point& a, const Point& b) = + (isOrthogonal) ? manhattanDist : euclideanDist; + std::vector<ANode> PENDING; // STL Vectors chosen because of rapid std::vector<ANode> DONE; // insertions/deletions at back, ANode Node, BestNode; // Temporary Node and BestNode bool bNodeFound = false; // Flag if node is found in container + int timestamp = 1; - tar->pathNext = NULL; + if (start == NULL) + { + start = src; + } + + Router *router = lineRef->router(); + if (router->RubberBandRouting && (start != src)) + { + COLA_ASSERT(router->IgnoreRegions == true); + + const PolyLine& currRoute = lineRef->route(); + VertInf *last = NULL; + int rIndx = 0; + while (last != start) + { + const Point& pnt = currRoute.at(rIndx); + bool isShape = (rIndx > 0); + VertID vID(pnt.id, isShape, pnt.vn); + +#ifdef PATHDEBUG + db_printf("/// %d %d %d\n", pnt.id, (int) isShape, pnt.vn); +#endif + VertInf *curr = router->vertices.getVertexByID(vID); + COLA_ASSERT(curr != NULL); + + Node = ANode(curr, timestamp++); + if (!last) + { + Node.g = 0; + Node.h = estimatedCost(lineRef, NULL, Node.inf->point, + tar->point); + Node.f = Node.g + Node.h; + } + else + { + double edgeDist = dist(BestNode.inf->point, curr->point); + + Node.g = BestNode.g + cost(lineRef, edgeDist, BestNode.inf, + Node.inf, DONE, BestNode.prevIndex); + + // Calculate the Heuristic. + Node.h = estimatedCost(lineRef, &(BestNode.inf->point), + Node.inf->point, tar->point); + + // The A* formula + Node.f = Node.g + Node.h; + + // Point parent to last BestNode (pushed onto DONE) + Node.prevIndex = DONE.size() - 1; + } + + if (curr != start) + { + BestNode = Node; + + DONE.push_back(BestNode); + } + else + { + PENDING.push_back(Node); + } - // Create the start node - Node = ANode(src); - Node.g = 0; - Node.h = dist(Node.inf->point, tar->point); - Node.f = Node.g + Node.h; - // Set a null parent, so cost function knows this is the first segment. - Node.pp = NULL; + rIndx++; + last = curr; + } + } + else + { + // Create the start node + Node = ANode(src, timestamp++); + Node.g = 0; + Node.h = estimatedCost(lineRef, NULL, Node.inf->point, tar->point); + Node.f = Node.g + Node.h; + // Set a null parent, so cost function knows this is the first segment. + + // Populate the PENDING container with the first location + PENDING.push_back(Node); + } + + tar->pathNext = NULL; - // Populate the PENDING container with the first location - PENDING.push_back(Node); // Create a heap from PENDING for sorting using std::make_heap; using std::push_heap; using std::pop_heap; make_heap( PENDING.begin(), PENDING.end() ); while (!PENDING.empty()) { - // Ascending sort based on overloaded operators below - sort_heap(PENDING.begin(), PENDING.end()); - - // Set the Node with lowest f value to BESTNODE + // Set the Node with lowest f value to BESTNODE. + // Since the ANode operator< is reversed, the head of the + // heap is the node with the lowest f value. BestNode = PENDING.front(); // Pop off the heap. Actually this moves the @@ -427,38 +505,118 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) // is not actually removed since the pop is to // the heap and not the container. pop_heap(PENDING.begin(), PENDING.end()); - - // Remove node from right (the value we pop_heap'd) PENDING.pop_back(); // Push the BestNode onto DONE - BestNode.inf->pathNext = BestNode.pp; DONE.push_back(BestNode); + VertInf *prevInf = (BestNode.prevIndex >= 0) ? + DONE[BestNode.prevIndex].inf : NULL; #if 0 - printf("Considering... "); - BestNode.ID->print(stdout); - printf(" - g: %3.1f h: %3.1f f: %3.1f back: ", BestNode.g, BestNode.h, - BestNode.f); - BestNode.pp.print(stdout); - printf("\n"); + db_printf("Considering... "); + db_printf(" %g %g ", BestNode.inf->point.x, BestNode.inf->point.y); + BestNode.inf->id.db_print(); + db_printf(" - g: %3.1f h: %3.1f back: ", BestNode.g, BestNode.h); + if (prevInf) + { + db_printf(" %g %g", prevInf->point.x, prevInf->point.y); + //prevInf->id.db_print(); + } + db_printf("\n"); +#endif + +#if defined(ASTAR_DEBUG) + if (router->avoid_screen) + { + int canx = 151; + int cany = 55; + int radius = 5; + ANode curr; + for (curr = BestNode; curr.prevIndex >= 0; + curr = DONE[curr.prevIndex]) + { + filledCircleRGBA(router->avoid_screen, + (int) curr.inf->point.x + canx, + (int) curr.inf->point.y + cany, + radius, 0, 0, 255, 128); + } + filledCircleRGBA(router->avoid_screen, + (int) BestNode.inf->point.x + canx, + (int) BestNode.inf->point.y + cany, + radius, 255, 0, 0, 255); + + SDL_Flip(router->avoid_screen); + //SDL_Delay(500); + + filledCircleRGBA(router->avoid_screen, + (int) BestNode.inf->point.x + canx, + (int) BestNode.inf->point.y + cany, + radius, 255, 255, 255, 255); + filledCircleRGBA(router->avoid_screen, + (int) BestNode.inf->point.x + canx, + (int) BestNode.inf->point.y + cany, + radius, 0, 255, 0, 128); + for (curr = BestNode; curr.prevIndex >= 0; + curr = DONE[curr.prevIndex]) + { + filledCircleRGBA(router->avoid_screen, + (int) curr.inf->point.x + canx, + (int) curr.inf->point.y + cany, + radius, 255, 255, 255, 255); + filledCircleRGBA(router->avoid_screen, + (int) curr.inf->point.x + canx, + (int) curr.inf->point.y + cany, + radius, 0, 255, 0, 128); + } + } #endif // If at destination, break and create path below if (BestNode.inf == tar) { +#ifdef PATHDEBUG + db_printf("Cost: %g\n", BestNode.f); +#endif //bPathFound = true; // arrived at destination... + + // Correct all the pathNext pointers. + ANode curr; + int currIndex = DONE.size() - 1; + for (curr = BestNode; curr.prevIndex > 0; + curr = DONE[curr.prevIndex]) + { + COLA_ASSERT(curr.prevIndex < currIndex); + curr.inf->pathNext = DONE[curr.prevIndex].inf; + currIndex = curr.prevIndex; + } + // Check that we've gone through the complete path. + COLA_ASSERT(curr.prevIndex == 0); + // Fill in the final pathNext pointer. + curr.inf->pathNext = DONE[curr.prevIndex].inf; + break; } // Check adjacent points in graph - EdgeInfList& visList = BestNode.inf->visList; - EdgeInfList::iterator finish = visList.end(); - for (EdgeInfList::iterator edge = visList.begin(); edge != finish; - ++edge) + EdgeInfList& visList = (!isOrthogonal) ? + BestNode.inf->visList : BestNode.inf->orthogVisList; + if (isOrthogonal) + { + // We would like to explore in a structured way, + // so sort the points in the visList... + CmpVisEdgeRotation compare(prevInf); + visList.sort(compare); + } + EdgeInfList::const_iterator finish = visList.end(); + for (EdgeInfList::const_iterator edge = visList.begin(); + edge != finish; ++edge) { - Node.inf = (*edge)->otherVert(BestNode.inf); + Node = ANode((*edge)->otherVert(BestNode.inf), timestamp++); + + // Set the index to the previous ANode that we reached + // this ANode through (the last BestNode pushed onto DONE). + Node.prevIndex = DONE.size() - 1; // Only check shape verticies, or the tar endpoint. if (!(Node.inf->id.isShape) && (Node.inf != tar)) @@ -466,6 +624,15 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) continue; } + VertInf *prevInf = (BestNode.prevIndex >= 0) ? + DONE[BestNode.prevIndex].inf : NULL; + + // Don't bother looking at the segment we just arrived along. + if (prevInf && (prevInf == Node.inf)) + { + continue; + } + double edgeDist = (*edge)->getDist(); if (edgeDist == 0) @@ -473,31 +640,49 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) continue; } - VertInf *prevInf = BestNode.inf->pathNext; + if (!router->_orthogonalRouting && + (!router->RubberBandRouting || (start == src)) && + (validateBendPoint(prevInf, BestNode.inf, Node.inf) == false)) + { + // The bendpoint is not valid, i.e., is a zigzag corner, so... + continue; + // For RubberBand routing we want to allow these routes and + // unwind them later, otherwise instead or unwinding, paths + // can go the *really* long way round. + } - Node.g = BestNode.g + cost(lineRef, edgeDist, prevInf, - BestNode.inf, Node.inf); + Node.g = BestNode.g + cost(lineRef, edgeDist, BestNode.inf, + Node.inf, DONE, BestNode.prevIndex); // Calculate the Heuristic. - Node.h = dist(Node.inf->point, tar->point); + Node.h = estimatedCost(lineRef, &(BestNode.inf->point), + Node.inf->point, tar->point); // The A* formula Node.f = Node.g + Node.h; - // Point parent to last BestNode (pushed onto DONE) - Node.pp = BestNode.inf; + +#if 0 + db_printf("-- Adding: %g %g ", Node.inf->point.x, + Node.inf->point.y); + Node.inf->id.db_print(); + db_printf(" - g: %3.1f h: %3.1f \n", Node.g, Node.h); +#endif bNodeFound = false; // Check to see if already on PENDING for (unsigned int i = 0; i < PENDING.size(); i++) { - if (Node.inf == PENDING.at(i).inf) - { // If already on PENDING - if (Node.g < PENDING.at(i).g) + ANode& ati = PENDING.at(i); + if ((Node.inf == ati.inf) && + (DONE[Node.prevIndex].inf == DONE[ati.prevIndex].inf)) + { + // If already on PENDING + if (Node.g < ati.g) { - PENDING.at(i).g = Node.g; - PENDING.at(i).f = Node.g + PENDING.at(i).h; - PENDING.at(i).pp = Node.pp; + PENDING[i] = Node; + + make_heap( PENDING.begin(), PENDING.end() ); } bNodeFound = true; break; @@ -508,15 +693,14 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) // Check to see if already on DONE for (unsigned int i = 0; i < DONE.size(); i++) { - if (Node.inf == DONE.at(i).inf) + ANode& ati = DONE.at(i); + if ((Node.inf == ati.inf) && + (DONE[Node.prevIndex].inf == DONE[ati.prevIndex].inf)) { // If on DONE, Which has lower gone? - if (Node.g < DONE.at(i).g) + if (Node.g < ati.g) { - DONE.at(i).g = Node.g; - DONE.at(i).f = Node.g + DONE.at(i).h; - DONE.at(i).pp = Node.pp; - DONE.at(i).inf->pathNext = Node.pp; + DONE[i] = Node; } bNodeFound = true; break; @@ -530,26 +714,24 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) PENDING.push_back(Node); // Push NewNode onto heap push_heap( PENDING.begin(), PENDING.end() ); - // Re-Assert heap, or will be short by one - make_heap( PENDING.begin(), PENDING.end() ); #if 0 + using std::cout; using std::endl; // Display PENDING and DONE containers (For Debugging) cout << "PENDING: "; - for (int i = 0; i < PENDING.size(); i++) + for (unsigned int i = 0; i < PENDING.size(); i++) { - cout << PENDING.at(i).x << "," << PENDING.at(i).y << ","; - cout << PENDING.at(i).g << "," << PENDING.at(i).h << " "; + cout << PENDING.at(i).g << "," << PENDING.at(i).h << ","; + cout << PENDING.at(i).inf << "," << PENDING.at(i).pp << " "; } cout << endl; cout << "DONE: "; - for (int i = 0; i < DONE.size(); i++) + for (unsigned int i = 0; i < DONE.size(); i++) { - cout << DONE.at(i).x << "," << DONE.at(i).y << ","; - cout << DONE.at(i).g << "," << DONE.at(i).h << " "; + cout << DONE.at(i).g << "," << DONE.at(i).h << ","; + cout << DONE.at(i).inf << "," << DONE.at(i).pp << " "; } cout << endl << endl; - int ch = _getch(); #endif } } @@ -559,75 +741,52 @@ static void aStarPath(ConnRef *lineRef, VertInf *src, VertInf *tar) // Returns the best path for the connector referred to by lineRef. // -// The path encoded in the pathNext links in each of the VerInfs +// The path encoded in the pathNext links in each of the VertInfs // backwards along the path, from the tar back to the source. // void makePath(ConnRef *lineRef, bool *flag) { + bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal); Router *router = lineRef->router(); VertInf *src = lineRef->src(); VertInf *tar = lineRef->dst(); + VertInf *start = lineRef->start(); - // If the connector hates crossings then we want to examine direct paths: - bool examineDirectPath = lineRef->doesHateCrossings(); - // TODO: Could be more efficient here. - EdgeInf *directEdge = EdgeInf::existingEdge(src, tar); - if (!(router->IncludeEndpoints) && directVis(src, tar)) - { - Point p = src->point; - Point q = tar->point; - - assert(directEdge == NULL); - - directEdge = new EdgeInf(src, tar); - tar->pathNext = src; - directEdge->setDist(dist(p, q)); - directEdge->addConn(flag); - - return; - } - else if (router->IncludeEndpoints && directEdge && - (directEdge->getDist() > 0) && !examineDirectPath) + if (isOrthogonal) { - tar->pathNext = src; - directEdge->addConn(flag); + aStarPath(lineRef, src, tar, start); } - else + else // if (!isOrthogonal) { - // Mark the path endpoints as not being able to see - // each other. This is true if we are here. - if (!(router->IncludeEndpoints) && router->InvisibilityGrph) - { - if (!directEdge) - { - directEdge = new EdgeInf(src, tar); - } - directEdge->addBlocker(0); - } - - if (router->UseAStarSearch) + EdgeInf *directEdge = EdgeInf::existingEdge(src, tar); + // If the connector hates crossings or there are clusters present, + // then we want to examine direct paths: + bool examineDirectPath = lineRef->doesHateCrossings() || + !(router->clusterRefs.empty()); + + if ((start == src) && directEdge && (directEdge->getDist() > 0) && + !examineDirectPath) { - aStarPath(lineRef, src, tar); + tar->pathNext = src; + directEdge->addConn(flag); } else { - dijkstraPath(lineRef, src, tar); + aStarPath(lineRef, src, tar, start); } + } #if 0 - PointMap::iterator t; - for (VertInf *t = vertices.connsBegin(); t != vertices.end(); - t = t->lstNext) - { - - t->id.print(); - printf(" -> "); - t->pathNext->id.print(); - printf("\n"); - } -#endif + for (VertInf *t = vertices.connsBegin(); t != vertices.end(); + t = t->lstNext) + { + t->id.db_print(); + db_printf(" -> "); + t->pathNext->id.db_print(); + db_printf("\n"); } +#endif } diff --git a/src/libavoid/makepath.h b/src/libavoid/makepath.h index 4d68a01e3..b40bfbc3d 100644 --- a/src/libavoid/makepath.h +++ b/src/libavoid/makepath.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef AVOID_MAKEPATH_H #define AVOID_MAKEPATH_H diff --git a/src/libavoid/orthogonal.cpp b/src/libavoid/orthogonal.cpp new file mode 100644 index 000000000..4a7b0af2d --- /dev/null +++ b/src/libavoid/orthogonal.cpp @@ -0,0 +1,2354 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + + +#include <cstdlib> +#include <cfloat> +#include <cmath> +#include <set> +#include <list> +#include <algorithm> + +#include "libavoid/router.h" +#include "libavoid/geomtypes.h" +#include "libavoid/shape.h" +#include "libavoid/orthogonal.h" +#include "libavoid/connector.h" +#include "libavoid/vpsc.h" +#include "libavoid/assertions.h" + +#ifdef LIBAVOID_SDL + #include <SDL_gfxPrimitives.h> +#endif + + +namespace Avoid { + + +static const double CHANNEL_MAX = 100000000; + +static const size_t XDIM = 0; +static const size_t YDIM = 1; + + +class ShiftSegment +{ + public: + // For shiftable segments. + ShiftSegment(ConnRef *conn, const size_t low, const size_t high, + bool isSBend, const size_t dim, double minLim, double maxLim) + : connRef(conn), + indexLow(low), + indexHigh(high), + sBend(isSBend), + fixed(false), + dimension(dim), + variable(NULL), + minSpaceLimit(minLim), + maxSpaceLimit(maxLim) + { + } + + // For fixed segments. + ShiftSegment(ConnRef *conn, const size_t low, const size_t high, + const size_t dim) + : connRef(conn), + indexLow(low), + indexHigh(high), + sBend(false), + fixed(true), + dimension(dim), + variable(NULL) + { + // This has no space to shift. + minSpaceLimit = lowPoint()[dim]; + maxSpaceLimit = lowPoint()[dim]; + } + + Point& lowPoint(void) + { + return connRef->displayRoute().ps[indexLow]; + } + + Point& highPoint(void) + { + return connRef->displayRoute().ps[indexHigh]; + } + + const Point& lowPoint(void) const + { + return connRef->displayRoute().ps[indexLow]; + } + + const Point& highPoint(void) const + { + return connRef->displayRoute().ps[indexHigh]; + } + + int fixedOrder(bool& isFixed) const + { + if (fixed) + { + isFixed = true; + return 0; + } + if (lowC()) + { + return 1; + } + else if (highC()) + { + return -1; + } + return 0; + } + + int order(void) const + { + if (lowC()) + { + return -1; + } + else if (highC()) + { + return 1; + } + return 0; + } + + bool operator<(const ShiftSegment& rhs) const + { + const Point& lowPt = lowPoint(); + const Point& rhsLowPt = rhs.lowPoint(); + + if (lowPt[dimension] != rhsLowPt[dimension]) + { + return lowPt[dimension] < rhsLowPt[dimension]; + } + return this < &rhs; + } + + // This counts segments that are colliear and share an endpoint as + // overlapping. This allows them to be nudged apart where possible. + bool overlapsWith(const ShiftSegment& rhs, const size_t dim) const + { + size_t altDim = (dim + 1) % 2; + const Point& lowPt = lowPoint(); + const Point& highPt = highPoint(); + const Point& rhsLowPt = rhs.lowPoint(); + const Point& rhsHighPt = rhs.highPoint(); + if ( (lowPt[altDim] <= rhsHighPt[altDim]) && + (rhsLowPt[altDim] <= highPt[altDim])) + { + if ( (minSpaceLimit <= rhs.maxSpaceLimit) && + (rhs.minSpaceLimit <= maxSpaceLimit)) + { + return true; + } + } + return false; + } + + ConnRef *connRef; + size_t indexLow; + size_t indexHigh; + bool sBend; + bool fixed; + size_t dimension; + Variable *variable; + double minSpaceLimit; + double maxSpaceLimit; + private: + bool lowC(void) const + { + // This is true if this is a cBend and its adjoining points + // are at lower positions. + if (!sBend && !fixed && (minSpaceLimit == lowPoint()[dimension])) + { + return true; + } + return false; + } + + bool highC(void) const + { + // This is true if this is a cBend and its adjoining points + // are at higher positions. + if (!sBend && !fixed && (maxSpaceLimit == lowPoint()[dimension])) + { + return true; + } + return false; + } +}; +typedef std::list<ShiftSegment> ShiftSegmentList; + +bool cmpShiftSegment(const ShiftSegment& u, const ShiftSegment& v) +{ + return u < v; +} + + +struct Node; +struct CmpNodePos { bool operator()(const Node* u, const Node* v) const; }; + +typedef std::set<Node*,CmpNodePos> NodeSet; +struct Node +{ + ShapeRef *v; + VertInf *c; + ShiftSegment *ss; + double pos; + double min[2], max[2]; + Node *firstAbove, *firstBelow; + NodeSet::iterator iter; + + Node(ShapeRef *v, const double p) + : v(v), + c(NULL), + ss(NULL), + pos(p), + firstAbove(NULL), + firstBelow(NULL) + { + //COLA_ASSERT(r->width()<1e40); + v->polygon().getBoundingRect(&min[0], &min[1], &max[0], &max[1]); + } + Node(VertInf *c, const double p) + : v(NULL), + c(c), + ss(NULL), + pos(p), + firstAbove(NULL), + firstBelow(NULL) + { + min[0] = max[0] = c->point.x; + min[1] = max[1] = c->point.y; + } + Node(ShiftSegment *ss, const double p) + : v(NULL), + c(NULL), + ss(ss), + pos(p), + firstAbove(NULL), + firstBelow(NULL) + { + // These values shouldn't ever be used, so they don't matter. + min[0] = max[0] = min[1] = max[1] = 0; + } + ~Node() + { + } + // Find the first Node above in the scanline that is a shape edge, + // and does not have an open or close event at this position (meaning + // it is just about to be removed). + double firstObstacleAbove(size_t dim) + { + Node *curr = firstAbove; + while (curr && (curr->ss || (curr->max[dim] > pos))) + { + curr = curr->firstAbove; + } + + if (curr) + { + return curr->max[dim]; + } + return -DBL_MAX; + } + // Find the first Node below in the scanline that is a shape edge, + // and does not have an open or close event at this position (meaning + // it is just about to be removed). + double firstObstacleBelow(size_t dim) + { + Node *curr = firstBelow; + while (curr && (curr->ss || (curr->min[dim] < pos))) + { + curr = curr->firstBelow; + } + + if (curr) + { + return curr->min[dim]; + } + return DBL_MAX; + } + // Mark all connector segments above in the scanline as being able + // to see to this shape edge. + void markShiftSegmentsAbove(size_t dim) + { + Node *curr = firstAbove; + while (curr && (curr->ss || (curr->pos > min[dim]))) + { + if (curr->ss && (curr->pos <= min[dim])) + { + curr->ss->maxSpaceLimit = + std::min(min[dim], curr->ss->maxSpaceLimit); + } + curr = curr->firstAbove; + } + } + // Mark all connector segments below in the scanline as being able + // to see to this shape edge. + void markShiftSegmentsBelow(size_t dim) + { + Node *curr = firstBelow; + while (curr && (curr->ss || (curr->pos < max[dim]))) + { + if (curr->ss && (curr->pos >= max[dim])) + { + curr->ss->minSpaceLimit = + std::max(max[dim], curr->ss->minSpaceLimit); + } + curr = curr->firstBelow; + } + } + bool findFirstPointAboveAndBelow(const size_t dim, double& firstAbovePos, + double& firstBelowPos, double& lastAbovePos, double& lastBelowPos) + { + bool clearVisibility = true; + firstAbovePos = -DBL_MAX; + firstBelowPos = DBL_MAX; + // We start looking left from the right side of the shape, + // and vice versa. + lastAbovePos = max[dim]; + lastBelowPos = min[dim]; + + // Find the first blocking edge above this point. Don't count the + // edges as we are travelling out of shapes we are inside, but then + // mark clearVisibility as false. + Node *curr = firstAbove; + while (curr && (curr->max[dim] > min[dim])) + { + lastAbovePos = std::min(curr->min[dim], lastAbovePos); + if ((curr->max[dim] >= min[dim]) && (curr->max[dim] <= max[dim])) + { + lastAbovePos = std::min(curr->max[dim], lastAbovePos); + } + lastBelowPos = std::max(curr->max[dim], lastBelowPos); + clearVisibility = false; + curr = curr->firstAbove; + } + if (curr) + { + firstAbovePos = curr->max[dim]; + } + while (curr) + { + // There might be a larger shape after this one in the ordering. + if (curr->max[dim] < min[dim]) + { + firstAbovePos = std::max(curr->max[dim], firstAbovePos); + } + curr = curr->firstAbove; + } + + // Find the first blocking edge below this point. Don't count the + // edges as we are travelling out of shapes we are inside, but then + // mark clearVisibility as false. + curr = firstBelow; + while (curr && (curr->min[dim] < max[dim])) + { + lastBelowPos = std::max(curr->max[dim], lastBelowPos); + if ((curr->min[dim] >= min[dim]) && (curr->min[dim] <= max[dim])) + { + lastBelowPos = std::max(curr->min[dim], lastBelowPos); + } + lastAbovePos = std::min(curr->min[dim], lastAbovePos); + clearVisibility = false; + curr = curr->firstBelow; + } + if (curr) + { + firstBelowPos = curr->min[dim]; + } + while (curr) + { + // There might be a larger shape after this one in the ordering. + if (curr->min[dim] > max[dim]) + { + firstBelowPos = std::min(curr->min[dim], firstBelowPos); + } + curr = curr->firstBelow; + } + + return clearVisibility; + } + double firstPointAbove(size_t dim) + { + Node *curr = firstAbove; + while (curr && (curr->max[dim] >= pos)) + { + curr = curr->firstAbove; + } + + if (curr) + { + return curr->max[dim]; + } + return -DBL_MAX; + } + double firstPointBelow(size_t dim) + { + Node *curr = firstBelow; + while (curr && (curr->min[dim] <= pos)) + { + curr = curr->firstBelow; + } + + if (curr) + { + return curr->min[dim]; + } + return DBL_MAX; + } + // This is a bit inefficient, but we won't need to do it once we have + // connection points. + bool isInsideShape(size_t dimension) + { + for (Node *curr = firstBelow; curr; curr = curr->firstBelow) + { + if ((curr->min[dimension] < pos) && (pos < curr->max[dimension])) + { + return true; + } + } + for (Node *curr = firstAbove; curr; curr = curr->firstAbove) + { + if ((curr->min[dimension] < pos) && (pos < curr->max[dimension])) + { + return true; + } + } + return false; + } +}; + + +bool CmpNodePos::operator() (const Node* u, const Node* v) const +{ + if (u->pos != v->pos) + { + return u->pos < v->pos; + } + + // Use the pointers to the base objects to differentiate them. + void *up = (u->v) ? (void *) u->v : + ((u->c) ? (void *) u->c : (void *) u->ss); + void *vp = (v->v) ? (void *) v->v : + ((v->c) ? (void *) v->c : (void *) v->ss); + return up < vp; +} + + +// Note: Open must come first. +typedef enum { + Open = 1, + SegOpen = 2, + ConnPoint = 3, + SegClose = 4, + Close = 5 +} EventType; + + +struct Event +{ + Event(EventType t, Node *v, double p) + : type(t), + v(v), + pos(p) + {}; + EventType type; + Node *v; + double pos; +}; + +Event **events; + + +// Used for quicksort. Must return <0, 0, or >0. +int compare_events(const void *a, const void *b) +{ + Event *ea = *(Event**) a; + Event *eb = *(Event**) b; + if (ea->pos != eb->pos) + { + return (ea->pos < eb->pos) ? -1 : 1; + } + if (ea->type != eb->type) + { + return ea->type - eb->type; + } + COLA_ASSERT(ea->v != eb->v); + return ea->v - eb->v; +} + + +// Returns a bitfield of the direction of visibility (in this dimension) +// made up of ConnDirDown (for visibility towards lower position values) +// and ConnDirUp (for visibility towards higher position values). +// +static ConnDirFlags getPosVertInfDirection(VertInf *v, size_t dim) +{ + if (dim == XDIM) // X-dimension + { + unsigned int dirs = v->visDirections & (ConnDirLeft | ConnDirRight); + if (dirs == (ConnDirLeft | ConnDirRight)) + { + return (ConnDirDown | ConnDirUp); + } + else if (dirs == ConnDirLeft) + { + return ConnDirDown; + } + else if (dirs == ConnDirRight) + { + return ConnDirUp; + } + } + else if (dim == YDIM) // Y-dimension + { + unsigned int dirs = v->visDirections & (ConnDirDown | ConnDirUp); + if (dirs == (ConnDirDown | ConnDirUp)) + { + return (ConnDirDown | ConnDirUp); + } + else if (dirs == ConnDirDown) + { + // For libavoid the Y-axis points downwards, so in terms of + // smaller or larger position values, Down is Up and vice versa. + return ConnDirUp; + } + else if (dirs == ConnDirUp) + { + // For libavoid the Y-axis points downwards, so in terms of + // smaller or larger position values, Down is Up and vice versa. + return ConnDirDown; + } + } + + // Can occur for ConnDirNone visibility. + return ConnDirNone; +} + + +struct PosVertInf +{ + PosVertInf(double p, VertInf *vI, ConnDirFlags d = ConnDirNone) + : pos(p), + vert(vI), + dir(d) + { + } + + bool operator<(const PosVertInf& rhs) const + { + if (pos != rhs.pos) + { + return pos < rhs.pos; + } + return vert < rhs.vert; + } + + double pos; + VertInf *vert; + + // A bitfield marking the direction of visibility (in this dimension) + // made up of ConnDirDown (for visibility towards lower position values) + // and ConnDirUp (for visibility towards higher position values). + // + ConnDirFlags dir; +}; + + +struct CmpVertInf +{ + bool operator()(const VertInf* u, const VertInf* v) const + { + // Comparator for VertSet, an ordered set of VertInf pointers. + // It is assumed vertical sets of points will all have the same + // x position and horizontal sets all share a y position, so this + // method can be used to sort both these sets. + COLA_ASSERT((u->point.x == v->point.x) || (u->point.y == v->point.y)); + if (u->point.x != v->point.x) + { + return u->point.x < v->point.x; + } + else if (u->point.y != v->point.y) + { + return u->point.y < v->point.y; + } + return u < v; + } +}; + + +typedef std::set<VertInf *, CmpVertInf> VertSet; + +// A set of points to break the line segment, +// along with vertices for these points. +typedef std::set<PosVertInf> BreakpointSet; + +// Temporary structure used to store the possible horizontal visibility +// lines arising from the vertical sweep. +class LineSegment +{ +public: + LineSegment(const double& b, const double& f, const double& p, + bool /*ss*/ = false, VertInf *bvi = NULL, VertInf *fvi = NULL) + : begin(b), + finish(f), + pos(p), + shapeSide(false) + { + COLA_ASSERT(begin < finish); + + if (bvi) + { + vertInfs.insert(bvi); + } + if (fvi) + { + vertInfs.insert(fvi); + } + } + + LineSegment(const double& bf, const double& p, VertInf *bfvi = NULL) + : begin(bf), + finish(bf), + pos(p), + shapeSide(false) + { + if (bfvi) + { + vertInfs.insert(bfvi); + } + } + + // Order by begin, pos, finish. + bool operator<(const LineSegment& rhs) const + { + if (begin != rhs.begin) + { + return begin < rhs.begin; + } + if (pos != rhs.pos) + { + return pos < rhs.pos; + } + if (finish != rhs.finish) + { + return finish < rhs.finish; + } + COLA_ASSERT(shapeSide == rhs.shapeSide); + return false; + } + + bool overlaps(const LineSegment& rhs) const + { + if ((begin == rhs.begin) && (pos == rhs.pos) && + (finish == rhs.finish)) + { + // Lines are exactly equal. + return true; + } + + if (pos == rhs.pos) + { + if (((begin >= rhs.begin) && (begin <= rhs.finish)) || + ((rhs.begin >= begin) && (rhs.begin <= finish)) ) + { + // They are colinear and overlap by some amount. + return true; + } + } + return false; + } + + void mergeVertInfs(const LineSegment& segment) + { + begin = std::min(begin, segment.begin); + finish = std::max(finish, segment.finish); + vertInfs.insert(segment.vertInfs.begin(), segment.vertInfs.end()); + } + + VertInf *beginVertInf(void) const + { + if (vertInfs.empty()) + { + return NULL; + } + return *vertInfs.begin(); + } + VertInf *finishVertInf(void) const + { + if (vertInfs.empty()) + { + return NULL; + } + return *vertInfs.rbegin(); + } + + VertInf *commitPositionX(Router *router, double posX) + { + VertInf *found = NULL; + for (VertSet::iterator v = vertInfs.begin(); + v != vertInfs.end(); ++v) + { + if ((*v)->point.x == posX) + { + found = *v; + break; + } + } + if (!found) + { + found = new VertInf(router, dummyOrthogID, Point(posX, pos)); + vertInfs.insert(found); + } + return found; + } + // Set begin endpoint vertex if none has been assigned. + void commitBegin(Router *router, VertInf *vert = NULL) + { + if (vert) + { + vertInfs.insert(vert); + } + + if (vertInfs.empty() || + ((*vertInfs.begin())->point.x != begin)) + { + vertInfs.insert(new + VertInf(router, dummyOrthogID, Point(begin, pos))); + } + } + + // Set begin endpoint vertex if none has been assigned. + void commitFinish(Router *router, VertInf *vert = NULL) + { + if (vert) + { + vertInfs.insert(vert); + } + + if (vertInfs.empty() || + ((*vertInfs.rbegin())->point.x != finish)) + { + vertInfs.insert(new + VertInf(router, dummyOrthogID, Point(finish, pos))); + } + } + + // Converts a section of the points list to a set of breakPoints. + // Returns the first of the intersection points occuring at finishPos. + VertSet::iterator addSegmentsUpTo(Router */*router*/, double finishPos) + { + VertSet::iterator firstIntersectionPt = vertInfs.end(); + for (VertSet::iterator vert = vertInfs.begin(); + vert != vertInfs.end(); ++vert) + { + if ((*vert)->point.x > finishPos) + { + // We're done. + break; + } + + breakPoints.insert(PosVertInf((*vert)->point.x, (*vert), + getPosVertInfDirection(*vert, XDIM))); + + if ((firstIntersectionPt == vertInfs.end()) && + ((*vert)->point.x == finishPos)) + { + firstIntersectionPt = vert; + } + } + // Returns the first of the intersection points at finishPos. + return firstIntersectionPt; + } + + // Add visibility edge(s) for this segment. There may be multiple if + // one of the endpoints is shared by multiple connector endpoints. + void addEdgeHorizontal(Router *router) + { + commitBegin(router); + commitFinish(router); + + addSegmentsUpTo(router, finish); + } + + // Add visibility edge(s) for this segment up until an intersection. + // Then, move the segment beginning to the intersection point, so we + // later only consider the remainder of the segment. + // There may be multiple segments added to the graph if the beginning + // endpoint of the segment is shared by multiple connector endpoints. + VertSet addEdgeHorizontalTillIntersection(Router *router, + LineSegment& vertLine) + { + VertSet intersectionSet; + + commitBegin(router); + + // Does a vertex already exist for this point. + commitPositionX(router, vertLine.pos); + + // Generate segments and set end iterator to the first point + // at the intersection position. + VertSet::iterator restBegin = addSegmentsUpTo(router, vertLine.pos); + + // Add the intersections points to intersectionSet. + VertSet::iterator restEnd = restBegin; + while ((restEnd != vertInfs.end()) && + (*restEnd)->point.x == vertLine.pos) + { + ++restEnd; + } + intersectionSet.insert(restBegin, restEnd); + + // Adjust segment to remove processed portion. + begin = vertLine.pos; + vertInfs.erase(vertInfs.begin(), restBegin); + + return intersectionSet; + } + + // Insert vertical breakpoints. + void insertBreakpointsBegin(Router *router, LineSegment& vertLine) + { + VertInf *vert = NULL; + if (pos == vertLine.begin && vertLine.beginVertInf()) + { + vert = vertLine.beginVertInf(); + } + else if (pos == vertLine.finish && vertLine.finishVertInf()) + { + vert = vertLine.finishVertInf(); + } + commitBegin(router, vert); + + for (VertSet::iterator v = vertInfs.begin(); + v != vertInfs.end(); ++v) + { + if ((*v)->point.x == begin) + { + vertLine.breakPoints.insert(PosVertInf(pos, *v, + getPosVertInfDirection(*v, YDIM))); + } + } + } + + // Insert vertical breakpoints. + void insertBreakpointsFinish(Router *router, LineSegment& vertLine) + { + VertInf *vert = NULL; + if (pos == vertLine.begin && vertLine.beginVertInf()) + { + vert = vertLine.beginVertInf(); + } + else if (pos == vertLine.finish && vertLine.finishVertInf()) + { + vert = vertLine.finishVertInf(); + } + commitFinish(router, vert); + + for (VertSet::iterator v = vertInfs.begin(); + v != vertInfs.end(); ++v) + { + if ((*v)->point.x == finish) + { + vertLine.breakPoints.insert(PosVertInf(pos, *v, + getPosVertInfDirection(*v, YDIM))); + } + } + } + void generateVisibilityEdgesFromBreakpointSet(Router *router, size_t dim) + { + if ((breakPoints.begin())->pos != begin) + { + if (!beginVertInf()) + { + Point point(pos, pos); + point[dim] = begin; + // Add begin point if it didn't intersect another line. + VertInf *vert = new VertInf(router, dummyOrthogID, point); + breakPoints.insert(PosVertInf(begin, vert)); + } + } + if ((breakPoints.rbegin())->pos != finish) + { + if (!finishVertInf()) + { + Point point(pos, pos); + point[dim] = finish; + // Add finish point if it didn't intersect another line. + VertInf *vert = new VertInf(router, dummyOrthogID, point); + breakPoints.insert(PosVertInf(finish, vert)); + } + } + + const bool orthogonal = true; + BreakpointSet::iterator vert, last; + for (vert = last = breakPoints.begin(); vert != breakPoints.end();) + { + BreakpointSet::iterator firstPrev = last; + while (last->vert->point[dim] != vert->vert->point[dim]) + { + COLA_ASSERT(vert != last); + // Assert points are not at the same position. + COLA_ASSERT(vert->vert->point != last->vert->point); + + if ( !(vert->vert->id.isShape || last->vert->id.isShape)) + { + // Here we have a pair of two endpoints that are both + // connector endpoints and both are inside a shape. + + // Give vert visibility back to the first non-connector + // endpoint vertex (i.e., the side of the shape). + BreakpointSet::iterator side = last; + while (!side->vert->id.isShape) + { + if (side == breakPoints.begin()) + { + break; + } + --side; + } + bool canSeeDown = (vert->dir & ConnDirDown); + if (canSeeDown && side->vert->id.isShape) + { + EdgeInf *edge = new + EdgeInf(side->vert, vert->vert, orthogonal); + edge->setDist(vert->vert->point[dim] - + side->vert->point[dim]); + } + + // Give last visibility back to the first non-connector + // endpoint vertex (i.e., the side of the shape). + side = vert; + while ((side != breakPoints.end()) && + !side->vert->id.isShape) + { + ++side; + } + bool canSeeUp = (last->dir & ConnDirUp); + if (canSeeUp && (side != breakPoints.end())) + { + EdgeInf *edge = new + EdgeInf(last->vert, side->vert, orthogonal); + edge->setDist(side->vert->point[dim] - + last->vert->point[dim]); + } + } + + // The normal case. + // + // Note: It's okay to give two connector endpoints visbility + // here since we only consider the partner endpoint as a + // candidate while searching if it is the other endpoint of + // the connector in question. + // + bool generateEdge = true; + if (!last->vert->id.isShape && !(last->dir & ConnDirUp)) + { + generateEdge = false; + } + else if (!vert->vert->id.isShape && !(vert->dir & ConnDirDown)) + { + generateEdge = false; + } + if (generateEdge) + { + EdgeInf *edge = + new EdgeInf(last->vert, vert->vert, orthogonal); + edge->setDist(vert->vert->point[dim] - + last->vert->point[dim]); + } + + ++last; + } + + ++vert; + + if ((vert != breakPoints.end()) && + (last->vert->point[dim] == vert->vert->point[dim])) + { + // Still looking at same pair, just reset prev number pointer. + last = firstPrev; + } + else + { + // vert has moved to the beginning of a number number group. + // Last is now in the right place, so do nothing. + } + } + } + + double begin; + double finish; + double pos; + bool shapeSide; + + VertSet vertInfs; + BreakpointSet breakPoints; +private: + // MSVC wants to generate the assignment operator and the default + // constructor, but fails. Therefore we declare them private and + // don't implement them. + LineSegment & operator=(LineSegment const &); + LineSegment(); +}; + +typedef std::list<LineSegment> SegmentList; + +class SegmentListWrapper +{ + public: + LineSegment *insert(LineSegment segment) + { + SegmentList::iterator found = _list.end(); + for (SegmentList::iterator curr = _list.begin(); + curr != _list.end(); ++curr) + { + if (curr->overlaps(segment)) + { + if (found != _list.end()) + { + // This is not the first segment that overlaps, + // so we need to merge and then delete an existing + // segment. + curr->mergeVertInfs(*found); + _list.erase(found); + found = curr; + } + else + { + // This is the first overlapping segment, so just + // merge the new segment with this one. + curr->mergeVertInfs(segment); + found = curr; + } + } + } + + if (found == _list.end()) + { + // Add this line. + _list.push_back(segment); + return &(_list.back()); + } + + return &(*found); + } + SegmentList& list(void) + { + return _list; + } + private: + SegmentList _list; +}; + + +// Given a router instance and a set of possible horizontal segments, and a +// possible vertical visibility segment, compute and add edges to the +// orthogonal visibility graph for all the visibility edges. +static void intersectSegments(Router *router, SegmentList& segments, + LineSegment& vertLine) +{ + COLA_ASSERT(vertLine.beginVertInf() == NULL); + COLA_ASSERT(vertLine.finishVertInf() == NULL); + for (SegmentList::iterator it = segments.begin(); it != segments.end(); ) + { + LineSegment& horiLine = *it; + + bool inVertSegRegion = ((vertLine.begin <= horiLine.pos) && + (vertLine.finish >= horiLine.pos)); + + if (horiLine.finish < vertLine.pos) + { + // Add horizontal visibility segment. + horiLine.addEdgeHorizontal(router); + + size_t dim = XDIM; // x-dimension + horiLine.generateVisibilityEdgesFromBreakpointSet(router, dim); + + // We've now swept past this horizontal segment, so delete. + it = segments.erase(it); + continue; + } + else if (horiLine.begin > vertLine.pos) + { + // We've yet to reach this segment in the sweep, so ignore. + ++it; + continue; + } + else if (horiLine.begin == vertLine.pos) + { + if (inVertSegRegion) + { + horiLine.insertBreakpointsBegin(router, vertLine); + } + } + else if (horiLine.finish == vertLine.pos) + { + if (inVertSegRegion) + { + // Add horizontal visibility segment. + horiLine.addEdgeHorizontal(router); + + horiLine.insertBreakpointsFinish(router, vertLine); + + size_t dim = XDIM; // x-dimension + horiLine.generateVisibilityEdgesFromBreakpointSet(router, dim); + + // And we've now finished with the segment, so delete. + it = segments.erase(it); + continue; + } + } + else + { + COLA_ASSERT(horiLine.begin < vertLine.pos); + COLA_ASSERT(horiLine.finish > vertLine.pos); + + if (inVertSegRegion) + { + // Add horizontal visibility segment. + VertSet intersectionVerts = + horiLine.addEdgeHorizontalTillIntersection( + router, vertLine); + + for (VertSet::iterator v = intersectionVerts.begin(); + v != intersectionVerts.end(); ++v) + { + vertLine.breakPoints.insert(PosVertInf(horiLine.pos, *v, + getPosVertInfDirection(*v, YDIM))); + } + } + } + ++it; + } + + // Split breakPoints set into visibility segments. + size_t dimension = YDIM; // y-dimension + vertLine.generateVisibilityEdgesFromBreakpointSet(router, dimension); +} + + +// Processes an event for the vertical sweep used for computing the static +// orthogonal visibility graph. This adds possible visibility sgments to +// the segments list. +// The first pass is adding the event to the scanline, the second is for +// processing the event and the third for removing it from the scanline. +static void processEventVert(Router *router, NodeSet& scanline, + SegmentListWrapper& segments, Event *e, unsigned int pass) +{ + Node *v = e->v; + + if ( ((pass == 1) && (e->type == Open)) || + ((pass == 2) && (e->type == ConnPoint)) ) + { + std::pair<NodeSet::iterator, bool> result = scanline.insert(v); + v->iter = result.first; + COLA_ASSERT(result.second); + + NodeSet::iterator it = v->iter; + // Work out neighbours + if (it != scanline.begin()) + { + Node *u = *(--it); + v->firstAbove = u; + u->firstBelow = v; + } + it = v->iter; + if (++it != scanline.end()) + { + Node *u = *it; + v->firstBelow = u; + u->firstAbove = v; + } + } + + if (pass == 2) + { + if ((e->type == Open) || (e->type == Close)) + { + // Shape edge positions. + double minShape = v->min[0]; + double maxShape = v->max[0]; + // As far as we can see. + double minLimit, maxLimit; + double minLimitMax, maxLimitMin; + v->findFirstPointAboveAndBelow(0, minLimit, maxLimit, + minLimitMax, maxLimitMin); + + // Only difference between Open and Close is whether the line + // segments are at the top or bottom of the shape. Decide here. + double lineY = (e->type == Open) ? v->min[1] : v->max[1]; + + if (minLimitMax >= maxLimitMin) + { + // Insert possible visibility segments. + VertInf *vI1 = new VertInf(router, dummyOrthogID, + Point(minShape, lineY)); + VertInf *vI2 = new VertInf(router, dummyOrthogID, + Point(maxShape, lineY)); + + // There are no overlapping shapes, so give full visibility. + if (minLimit < minShape) + { + segments.insert(LineSegment(minLimit, minShape, lineY, + true, NULL, vI1)); + } + segments.insert(LineSegment(minShape, maxShape, lineY, + true, vI1, vI2)); + if (maxShape < maxLimit) + { + segments.insert(LineSegment(maxShape, maxLimit, lineY, + true, vI2, NULL)); + } + } + else + { + if ((minLimitMax > minLimit) && (minLimitMax >= minShape)) + { + segments.insert(LineSegment(minLimit, minLimitMax, lineY, + true, NULL, NULL)); + } + if ((maxLimitMin < maxLimit) && (maxLimitMin <= maxShape)) + { + segments.insert(LineSegment(maxLimitMin, maxLimit, lineY, + true, NULL, NULL)); + } + } + } + else if (e->type == ConnPoint) + { + // Connection point. + VertInf *centreVert = e->v->c; + Point& cp = centreVert->point; + + // As far as we can see. + double minLimit = v->firstPointAbove(0); + double maxLimit = v->firstPointBelow(0); + bool inShape = v->isInsideShape(0); + + LineSegment *line1 = NULL, *line2 = NULL; + if (!inShape || (centreVert->visDirections & ConnDirLeft)) + { + line1 = segments.insert(LineSegment(minLimit, cp.x, e->pos, + true, NULL, centreVert)); + } + if (!inShape || (centreVert->visDirections & ConnDirRight)) + { + line2 = segments.insert(LineSegment(cp.x, maxLimit, e->pos, + true, centreVert, NULL)); + } + if (!line1 && !line2) + { + // Add a point segment for the centre point. + segments.insert(LineSegment(cp.x, e->pos, centreVert)); + } + + if (!inShape) + { + // This is not contained within a shape so add a normal + // visibility graph point here too (since paths won't route + // *through* connector endpoint vertices). + if (line1 || line2) + { + VertInf *cent = new VertInf(router, dummyOrthogID, cp); + if (line1) + { + line1->vertInfs.insert(cent); + } + if (line2) + { + line2->vertInfs.insert(cent); + } + } + } + } + } + + if ( ((pass == 3) && (e->type == Close)) || + ((pass == 2) && (e->type == ConnPoint)) ) + { + // Clean up neighbour pointers. + Node *l = v->firstAbove, *r = v->firstBelow; + if (l != NULL) + { + l->firstBelow = v->firstBelow; + } + if (r != NULL) + { + r->firstAbove = v->firstAbove; + } + + if (e->type == ConnPoint) + { + scanline.erase(v->iter); + delete v; + } + else // if (e->type == Close) + { + size_t result; + result = scanline.erase(v); + COLA_ASSERT(result == 1); + delete v; + } + } +} + + +// Processes an event for the vertical sweep used for computing the static +// orthogonal visibility graph. This adds possible visibility sgments to +// the segments list. +// The first pass is adding the event to the scanline, the second is for +// processing the event and the third for removing it from the scanline. +static void processEventHori(Router */*router*/, NodeSet& scanline, + SegmentListWrapper& segments, Event *e, unsigned int pass) +{ + Node *v = e->v; + + if ( ((pass == 1) && (e->type == Open)) || + ((pass == 2) && (e->type == ConnPoint)) ) + { + std::pair<NodeSet::iterator, bool> result = scanline.insert(v); + v->iter = result.first; + COLA_ASSERT(result.second); + + NodeSet::iterator it = v->iter; + // Work out neighbours + if (it != scanline.begin()) + { + Node *u = *(--it); + v->firstAbove = u; + u->firstBelow = v; + } + it = v->iter; + if (++it != scanline.end()) + { + Node *u = *it; + v->firstBelow = u; + u->firstAbove = v; + } + } + + if (pass == 2) + { + if ((e->type == Open) || (e->type == Close)) + { + // Shape edge positions. + double minShape = v->min[1]; + double maxShape = v->max[1]; + // As far as we can see. + double minLimit, maxLimit; + double minLimitMax, maxLimitMin; + v->findFirstPointAboveAndBelow(1, minLimit, maxLimit, + minLimitMax, maxLimitMin); + + // Only difference between Open and Close is whether the line + // segments are at the left or right of the shape. Decide here. + double lineX = (e->type == Open) ? v->min[0] : v->max[0]; + + if (minLimitMax >= maxLimitMin) + { + LineSegment vertSeg = LineSegment(minLimit, maxLimit, lineX); + segments.insert(vertSeg); + } + else + { + if ((minLimitMax > minLimit) && (minLimitMax >= minShape)) + { + LineSegment vertSeg = + LineSegment(minLimit, minLimitMax, lineX); + segments.insert(vertSeg); + } + if ((maxLimitMin < maxLimit) && (maxLimitMin <= maxShape)) + { + LineSegment vertSeg = + LineSegment(maxLimitMin, maxLimit, lineX); + segments.insert(vertSeg); + } + } + } + else if (e->type == ConnPoint) + { + // Connection point. + VertInf *centreVert = e->v->c; + Point& cp = centreVert->point; + + // As far as we can see. + double minLimit = v->firstPointAbove(1); + double maxLimit = v->firstPointBelow(1); + bool inShape = v->isInsideShape(1); + + if (!inShape || (centreVert->visDirections & ConnDirUp)) + { + segments.insert(LineSegment(minLimit, cp.y, e->pos)); + } + if (!inShape || (centreVert->visDirections & ConnDirDown)) + { + segments.insert(LineSegment(cp.y, maxLimit, e->pos)); + } + } + } + + if ( ((pass == 3) && (e->type == Close)) || + ((pass == 2) && (e->type == ConnPoint)) ) + { + // Clean up neighbour pointers. + Node *l = v->firstAbove, *r = v->firstBelow; + if (l != NULL) + { + l->firstBelow = v->firstBelow; + } + if (r != NULL) + { + r->firstAbove = v->firstAbove; + } + + if (e->type == ConnPoint) + { + scanline.erase(v->iter); + delete v; + } + else // if (e->type == Close) + { + size_t result; + result = scanline.erase(v); + COLA_ASSERT(result == 1); + delete v; + } + } +} + + +extern void generateStaticOrthogonalVisGraph(Router *router) +{ + const size_t n = router->shapeRefs.size(); + const unsigned cpn = router->vertices.connsSize(); + // Set up the events for the vertical sweep. + size_t totalEvents = (2 * n) + cpn; + events = new Event*[totalEvents]; + unsigned ctr = 0; + ShapeRefList::iterator shRefIt = router->shapeRefs.begin(); + for (unsigned i = 0; i < n; i++) + { + ShapeRef *shRef = *shRefIt; + double minX, minY, maxX, maxY; + shRef->polygon().getBoundingRect(&minX, &minY, &maxX, &maxY); + double midX = minX + ((maxX - minX) / 2); + Node *v = new Node(shRef, midX); + events[ctr++] = new Event(Open, v, minY); + events[ctr++] = new Event(Close, v, maxY); + + ++shRefIt; + } + for (VertInf *curr = router->vertices.connsBegin(); + curr && (curr != router->vertices.shapesBegin()); + curr = curr->lstNext) + { + Point& point = curr->point; + + Node *v = new Node(curr, point.x); + events[ctr++] = new Event(ConnPoint, v, point.y); + } + qsort((Event*)events, (size_t) totalEvents, sizeof(Event*), compare_events); + + // Process the vertical sweep. + // We do multiple passes over sections of the list so we can add relevant + // entries to the scanline that might follow, before process them. + SegmentListWrapper segments; + NodeSet scanline; + double thisPos = (totalEvents > 0) ? events[0]->pos : 0; + unsigned int posStartIndex = 0; + unsigned int posFinishIndex = 0; + for (unsigned i = 0; i <= totalEvents; ++i) + { + // If we have finished the current scanline or all events, then we + // process the events on the current scanline in a couple of passes. + if ((i == totalEvents) || (events[i]->pos != thisPos)) + { + posFinishIndex = i; + for (int pass = 2; pass <= 3; ++pass) + { + for (unsigned j = posStartIndex; j < posFinishIndex; ++j) + { + processEventVert(router, scanline, segments, + events[j], pass); + } + } + + if (i == totalEvents) + { + // We have cleaned up, so we can now break out of loop. + break; + } + + thisPos = events[i]->pos; + posStartIndex = i; + } + + // Do the first sweep event handling -- building the correct + // structure of the scanline. + const int pass = 1; + processEventVert(router, scanline, segments, events[i], pass); + } + COLA_ASSERT(scanline.size() == 0); + for (unsigned i = 0; i < totalEvents; ++i) + { + delete events[i]; + } + + segments.list().sort(); + + // Set up the events for the horizontal sweep. + SegmentListWrapper vertSegments; + ctr = 0; + shRefIt = router->shapeRefs.begin(); + for (unsigned i = 0; i < n; i++) + { + ShapeRef *shRef = *shRefIt; + double minX, minY, maxX, maxY; + shRef->polygon().getBoundingRect(&minX, &minY, &maxX, &maxY); + double midY = minY + ((maxY - minY) / 2); + Node *v = new Node(shRef, midY); + events[ctr++] = new Event(Open, v, minX); + events[ctr++] = new Event(Close, v, maxX); + + ++shRefIt; + } + for (VertInf *curr = router->vertices.connsBegin(); + curr && (curr != router->vertices.shapesBegin()); + curr = curr->lstNext) + { + Point& point = curr->point; + + Node *v = new Node(curr, point.y); + events[ctr++] = new Event(ConnPoint, v, point.x); + } + qsort((Event*)events, (size_t) totalEvents, sizeof(Event*), compare_events); + + // Process the horizontal sweep + thisPos = (totalEvents > 0) ? events[0]->pos : 0; + posStartIndex = 0; + posFinishIndex = 0; + for (unsigned i = 0; i <= totalEvents; ++i) + { + // If we have finished the current scanline or all events, then we + // process the events on the current scanline in a couple of passes. + if ((i == totalEvents) || (events[i]->pos != thisPos)) + { + posFinishIndex = i; + for (int pass = 2; pass <= 3; ++pass) + { + for (unsigned j = posStartIndex; j < posFinishIndex; ++j) + { + processEventHori(router, scanline, vertSegments, + events[j], pass); + } + } + + // Process the merged line segments. + vertSegments.list().sort(); + for (SegmentList::iterator curr = vertSegments.list().begin(); + curr != vertSegments.list().end(); ++curr) + { + intersectSegments(router, segments.list(), *curr); + } + vertSegments.list().clear(); + + if (i == totalEvents) + { + // We have cleaned up, so we can now break out of loop. + break; + } + + thisPos = events[i]->pos; + posStartIndex = i; + } + + // Do the first sweep event handling -- building the correct + // structure of the scanline. + const int pass = 1; + processEventHori(router, scanline, vertSegments, events[i], pass); + } + COLA_ASSERT(scanline.size() == 0); + for (unsigned i = 0; i < totalEvents; ++i) + { + delete events[i]; + } + delete [] events; + + // Add portions of the horizontal line that are after the final vertical + // position we considered. + for (SegmentList::iterator it = segments.list().begin(); + it != segments.list().end(); ) + { + LineSegment& horiLine = *it; + + horiLine.addEdgeHorizontal(router); + + size_t dim = XDIM; // x-dimension + horiLine.generateVisibilityEdgesFromBreakpointSet(router, dim); + + it = segments.list().erase(it); + } +} + + +//============================================================================ +// Path Adjustment code +//============================================================================ + + + + +// Processes sweep events used to determine each horizontal and vertical +// line segment in a connector's channel of visibility. +// Four calls to this function are made at each position by the scanline: +// 1) Handle all Close event processing. +// 2) Remove Close event objects from the scanline. +// 3) Add Open event objects to the scanline. +// 4) Handle all Open event processing. +// +static void processShiftEvent(Router */*router*/, NodeSet& scanline, + ShiftSegmentList& /*segments*/, Event *e, size_t dim, + unsigned int pass) +{ + Node *v = e->v; + + if ( ((pass == 3) && (e->type == Open)) || + ((pass == 3) && (e->type == SegOpen)) ) + { + std::pair<NodeSet::iterator, bool> result = scanline.insert(v); + v->iter = result.first; + COLA_ASSERT(result.second); + + NodeSet::iterator it = v->iter; + // Work out neighbours + if (it != scanline.begin()) + { + Node *u = *(--it); + v->firstAbove = u; + u->firstBelow = v; + } + it = v->iter; + if (++it != scanline.end()) + { + Node *u = *it; + v->firstBelow = u; + u->firstAbove = v; + } + } + + if ( ((pass == 4) && (e->type == Open)) || + ((pass == 4) && (e->type == SegOpen)) || + ((pass == 1) && (e->type == SegClose)) || + ((pass == 1) && (e->type == Close)) ) + { + if (v->ss) + { + // As far as we can see. + double minLimit = v->firstObstacleAbove(dim); + double maxLimit = v->firstObstacleBelow(dim); + + v->ss->minSpaceLimit = + std::max(minLimit, v->ss->minSpaceLimit); + v->ss->maxSpaceLimit = + std::min(maxLimit, v->ss->maxSpaceLimit); + } + else + { + v->markShiftSegmentsAbove(dim); + v->markShiftSegmentsBelow(dim); + } + } + + if ( ((pass == 2) && (e->type == SegClose)) || + ((pass == 2) && (e->type == Close)) ) + { + // Clean up neighbour pointers. + Node *l = v->firstAbove, *r = v->firstBelow; + if (l != NULL) + { + l->firstBelow = v->firstBelow; + } + if (r != NULL) + { + r->firstAbove = v->firstAbove; + } + + size_t result; + result = scanline.erase(v); + COLA_ASSERT(result == 1); + delete v; + } +} + + +static void buildOrthogonalChannelInfo(Router *router, + const size_t dim, ShiftSegmentList& segmentList) +{ + if (router->routingPenalty(segmentPenalty) == 0) + { + // This code assumes the routes are pretty optimal, so we don't + // do this adjustment if the routes have no segment penalty. + return; + } + + size_t altDim = (dim + 1) % 2; + // For each connector. + for (ConnRefList::const_iterator curr = router->connRefs.begin(); + curr != router->connRefs.end(); ++curr) + { + if ((*curr)->routingType() != ConnType_Orthogonal) + { + continue; + } + Polygon& displayRoute = (*curr)->displayRoute(); + // Determine all line segments that we are interested in shifting. + // We don't consider the first or last segment of a path. + for (size_t i = 1; i < displayRoute.size(); ++i) + { + if (displayRoute.ps[i - 1][dim] == displayRoute.ps[i][dim]) + { + // It's a segment in the dimension we are processing, + size_t indexLow = i - 1; + size_t indexHigh = i; + if (displayRoute.ps[i - 1][altDim] > displayRoute.ps[i][altDim]) + { + indexLow = i; + indexHigh = i - 1; + } + COLA_ASSERT(displayRoute.at(indexLow)[altDim] < + displayRoute.at(indexHigh)[altDim]); + + if ((i == 1) || ((i + 1) == displayRoute.size())) + { + // The first and last segment of a connector can't be + // shifted. We call them fixed segments. Note: this + // will change if we later allow connection channels. + segmentList.push_back( + ShiftSegment(*curr, indexLow, indexHigh, dim)); + continue; + } + + // The segment probably has space to be shifted. + double minLim = -CHANNEL_MAX; + double maxLim = CHANNEL_MAX; + bool isSBend = false; + + double prevPos = displayRoute.ps[i - 2][dim]; + double nextPos = displayRoute.ps[i + 1][dim]; + if (((prevPos < displayRoute.ps[i][dim]) && + (nextPos > displayRoute.ps[i][dim])) + || + ((prevPos > displayRoute.ps[i][dim]) && + (nextPos < displayRoute.ps[i][dim])) ) + { + isSBend = true; + + // Determine limits if the s-bend is not due to an + // obstacle. In this case we need to limit the channel + // to the span of the adjoining segments to this one. + if ((prevPos < displayRoute.ps[i][dim]) && + (nextPos > displayRoute.ps[i][dim])) + { + minLim = std::max(minLim, prevPos); + maxLim = std::min(maxLim, nextPos); + } + else + { + minLim = std::max(minLim, nextPos); + maxLim = std::min(maxLim, prevPos); + } + } + else + { + // isCBend: Both adjoining segments are in the same + // direction. We indicate this for later by setting + // the maxLim or minLim to the segment position. + if (prevPos < displayRoute.ps[i][dim]) + { + minLim = displayRoute.ps[i][dim]; + } + else + { + maxLim = displayRoute.ps[i][dim]; + } + } + + segmentList.push_back(ShiftSegment(*curr, indexLow, + indexHigh, isSBend, dim, minLim, maxLim)); + } + } + } + if (segmentList.empty()) + { + // There are no segments, so we can just return now. + return; + } + + // Do a sweep and shift these segments. + const size_t n = router->shapeRefs.size(); + const size_t cpn = segmentList.size(); + // Set up the events for the sweep. + size_t totalEvents = 2 * (n + cpn); + events = new Event*[totalEvents]; + unsigned ctr = 0; + ShapeRefList::iterator shRefIt = router->shapeRefs.begin(); + for (unsigned i = 0; i < n; i++) + { + ShapeRef *shRef = *shRefIt; + Point min, max; + shRef->polygon().getBoundingRect(&min.x, &min.y, &max.x, &max.y); + double mid = min[dim] + ((max[dim] - min[dim]) / 2); + Node *v = new Node(shRef, mid); + events[ctr++] = new Event(Open, v, min[altDim]); + events[ctr++] = new Event(Close, v, max[altDim]); + + ++shRefIt; + } + for (ShiftSegmentList::iterator curr = segmentList.begin(); + curr != segmentList.end(); ++curr) + { + const Point& lowPt = curr->lowPoint(); + const Point& highPt = curr->highPoint(); + + COLA_ASSERT(lowPt[dim] == highPt[dim]); + COLA_ASSERT(lowPt[altDim] < highPt[altDim]); + Node *v = new Node(&(*curr), lowPt[dim]); + events[ctr++] = new Event(SegOpen, v, lowPt[altDim]); + events[ctr++] = new Event(SegClose, v, highPt[altDim]); + } + qsort((Event*)events, (size_t) totalEvents, sizeof(Event*), compare_events); + + // Process the sweep. + // We do multiple passes over sections of the list so we can add relevant + // entries to the scanline that might follow, before process them. + NodeSet scanline; + double thisPos = (totalEvents > 0) ? events[0]->pos : 0; + unsigned int posStartIndex = 0; + unsigned int posFinishIndex = 0; + for (unsigned i = 0; i <= totalEvents; ++i) + { + // If we have finished the current scanline or all events, then we + // process the events on the current scanline in a couple of passes. + if ((i == totalEvents) || (events[i]->pos != thisPos)) + { + posFinishIndex = i; + for (int pass = 2; pass <= 4; ++pass) + { + for (unsigned j = posStartIndex; j < posFinishIndex; ++j) + { + processShiftEvent(router, scanline, segmentList, events[j], + dim, pass); + } + } + + if (i == totalEvents) + { + // We have cleaned up, so we can now break out of loop. + break; + } + + thisPos = events[i]->pos; + posStartIndex = i; + } + + // Do the first sweep event handling -- building the correct + // structure of the scanline. + const int pass = 1; + processShiftEvent(router, scanline, segmentList, events[i], + dim, pass); + } + COLA_ASSERT(scanline.size() == 0); + for (unsigned i = 0; i < totalEvents; ++i) + { + delete events[i]; + } + delete [] events; +} + + +static void simplifyOrthogonalRoutes(Router *router) +{ + // Simplify routes. + for (ConnRefList::const_iterator curr = router->connRefs.begin(); + curr != router->connRefs.end(); ++curr) + { + if ((*curr)->routingType() != ConnType_Orthogonal) + { + continue; + } + (*curr)->set_route((*curr)->displayRoute().simplify()); + } +} + + +static void buildOrthogonalNudgingOrderInfo(Router *router, + PtOrderMap& pointOrders) +{ + // Simplify routes. + simplifyOrthogonalRoutes(router); + + int crossingsN = 0; + + // Do segment splitting. + for (ConnRefList::const_iterator curr = router->connRefs.begin(); + curr != router->connRefs.end(); ++curr) + { + if ((*curr)->routingType() != ConnType_Orthogonal) + { + continue; + } + ConnRef *conn = *curr; + + for (ConnRefList::const_iterator curr2 = router->connRefs.begin(); + curr2 != router->connRefs.end(); ++curr2) + { + if ((*curr2)->routingType() != ConnType_Orthogonal) + { + continue; + } + ConnRef *conn2 = *curr2; + + if (conn == conn2) + { + continue; + } + + Avoid::Polygon& route = conn->displayRoute(); + Avoid::Polygon& route2 = conn2->displayRoute(); + splitBranchingSegments(route2, true, route); + } + } + + for (ConnRefList::const_iterator curr = router->connRefs.begin(); + curr != router->connRefs.end(); ++curr) + { + if ((*curr)->routingType() != ConnType_Orthogonal) + { + continue; + } + ConnRef *conn = *curr; + + for (ConnRefList::const_iterator curr2 = curr; + curr2 != router->connRefs.end(); ++curr2) + { + if ((*curr2)->routingType() != ConnType_Orthogonal) + { + continue; + } + ConnRef *conn2 = *curr2; + + if (conn == conn2) + { + continue; + } + + Avoid::Polygon& route = conn->displayRoute(); + Avoid::Polygon& route2 = conn2->displayRoute(); + bool checkForBranchingSegments = false; + int crossings = 0; + for (size_t i = 1; i < route.size(); ++i) + { + const bool finalSegment = ((i + 1) == route.size()); + crossings += countRealCrossings(route2, true, route, i, + checkForBranchingSegments, finalSegment, NULL, + &pointOrders, conn2, conn).first; + } + if (crossings > 0) + { + crossingsN += crossings; + } + } + } + + // Sort the point orders. + PtOrderMap::iterator finish = pointOrders.end(); + for (PtOrderMap::iterator it = pointOrders.begin(); it != finish; ++it) + { + //const VertID& ptID = it->first; + PtOrder& order = it->second; + + for (size_t dim = XDIM; dim <= YDIM; ++dim) + { + order.sort(dim); + } + } +} + + +class CmpLineOrder +{ + public: + CmpLineOrder(PtOrderMap& ord, const size_t dim) + : orders(ord), + dimension(dim) + { + } + bool operator()(const ShiftSegment& lhs, const ShiftSegment& rhs, + bool *comparable = NULL) const + { + if (comparable) + { + *comparable = true; + } + Point lhsLow = lhs.lowPoint(); + Point rhsLow = rhs.lowPoint(); +#ifndef NDEBUG + const Point& lhsHigh = lhs.highPoint(); + const Point& rhsHigh = rhs.highPoint(); +#endif + size_t altDim = (dimension + 1) % 2; + + COLA_ASSERT(lhsLow[dimension] == lhsHigh[dimension]); + COLA_ASSERT(rhsLow[dimension] == rhsHigh[dimension]); + + if (lhsLow[dimension] != rhsLow[dimension]) + { + return lhsLow[dimension] < rhsLow[dimension]; + } + + // If one of these is fixed, then determine order based on + // fixed segment, that is, order so the fixed segment doesn't + // block movement. + bool oneIsFixed = false; + const int lhsFixedOrder = lhs.fixedOrder(oneIsFixed); + const int rhsFixedOrder = rhs.fixedOrder(oneIsFixed); + if (oneIsFixed && (lhsFixedOrder != rhsFixedOrder)) + { + return lhsFixedOrder < rhsFixedOrder; + } + + // C-bends that did not have a clear order with s-bends might + // not have a good ordering here, so compare their order in + // terms of C-bend direction and S-bends and use that if it + // differs for the two segments. + const int lhsOrder = lhs.order(); + const int rhsOrder = rhs.order(); + if (lhsOrder != rhsOrder) + { + return lhsOrder < rhsOrder; + } + + // Need to index using the original point into the map, so find it. + Point& unchanged = (lhsLow[altDim] > rhsLow[altDim]) ? + lhsLow : rhsLow; + + PtOrder& lowOrder = orders[unchanged]; + int lhsPos = lowOrder.positionFor(lhs.connRef, dimension); + int rhsPos = lowOrder.positionFor(rhs.connRef, dimension); + if ((lhsPos == -1) || (rhsPos == -1)) + { + // A value for rhsPos or lhsPos mean the points are not directly + // comparable, meaning they are at the same position but cannot + // overlap (they are just collinear. The relative order for + // these segments is not important since we do not constrain + // them against each other. + //COLA_ASSERT(lhs.overlapsWith(rhs, dimension) == false); + // We do need to be consistent though. + if (comparable) + { + *comparable = false; + } + return lhsLow[altDim] < rhsLow[altDim]; + } + + return lhsPos < rhsPos; + } + + PtOrderMap& orders; + const size_t dimension; +}; + + +// We can use the normaal sort algorithm for lists since it is not possible +// to comapre all elements, but there will be an ordering defined between +// most of the elements. Hence we order these, using insertion sort, and +// the case of them not being able to be compared is handled by not setting +// up any constraints between such segments when doing the nudging. +// +static ShiftSegmentList linesort(ShiftSegmentList origList, + CmpLineOrder& comparison) +{ + ShiftSegmentList resultList; + + while (!origList.empty()) + { + // Get and remove the first element from the origList. + ShiftSegment segment = origList.front(); + origList.pop_front(); + + // Find the insertion point in the resultList. + ShiftSegmentList::iterator curr; + for (curr = resultList.begin(); curr != resultList.end(); ++curr) + { + bool comparable = false; + bool lessThan = comparison(segment, *curr, &comparable); + + if (comparable && lessThan) + { + // If it is comparable and lessThan, then we have found the + // insertion point. + break; + } + } + + // Insert the element into the reultList at the required point. + resultList.insert(curr, segment); + } + + return resultList; +} + + +typedef std::list<ShiftSegment *> ShiftSegmentPtrList; + + +static void nudgeOrthogonalRoutes(Router *router, size_t dimension, + PtOrderMap& pointOrders, ShiftSegmentList& segmentList) +{ + // Do the actual nudging. + ShiftSegmentList currentRegion; + while (!segmentList.empty()) + { + // Take a reference segment + ShiftSegment& currentSegment = segmentList.front(); + // Then, find the segments that overlap this one. + currentRegion.clear(); + currentRegion.push_back(currentSegment); + segmentList.erase(segmentList.begin()); + for (ShiftSegmentList::iterator curr = segmentList.begin(); + curr != segmentList.end(); ) + { + bool overlaps = false; + for (ShiftSegmentList::iterator curr2 = currentRegion.begin(); + curr2 != currentRegion.end(); ++curr2) + { + if (curr->overlapsWith(*curr2, dimension)) + { + overlaps = true; + break; + } + } + if (overlaps) + { + currentRegion.push_back(*curr); + segmentList.erase(curr); + // Consider segments from the beginning, since we mave have + // since passed segments that overlap with the new set. + curr = segmentList.begin(); + } + else + { + ++curr; + } + } + CmpLineOrder lineSortComp(pointOrders, dimension); + currentRegion = linesort(currentRegion, lineSortComp); + + if (currentRegion.size() == 1) + { + // Save creating the solver instance if there is just one + // immovable segment. + if (!currentRegion.front().sBend) + { + continue; + } + } + + // Process these segments. + Variables vs; + Constraints cs; + ShiftSegmentPtrList prevVars; + // IDs: + const int freeID = 0; + const int fixedID = 1; + // Weights: + double freeWeight = 0.00001; + double strongWeight = 0.001; + double fixedWeight = 100000; + //printf("-------------------------------------------------------\n"); + //printf("Nudge -- size: %d\n", (int) currentRegion.size()); + for (ShiftSegmentList::iterator currSegment = currentRegion.begin(); + currSegment != currentRegion.end(); ++currSegment) + { + Point& lowPt = currSegment->lowPoint(); + + // Create a solver variable for the position of this segment. + int varID = freeID; + double idealPos = lowPt[dimension]; + double weight = freeWeight; + if (currSegment->sBend) + { + COLA_ASSERT(currSegment->minSpaceLimit > -CHANNEL_MAX); + COLA_ASSERT(currSegment->maxSpaceLimit < CHANNEL_MAX); + + // For s-bends, take the middle as ideal. + idealPos = currSegment->minSpaceLimit + + ((currSegment->maxSpaceLimit - + currSegment->minSpaceLimit) / 2); + } + else if (currSegment->fixed) + { + // Fixed segments shouldn't get moved. + weight = fixedWeight; + varID = fixedID; + } + else + { + // Set a higher weight for c-bends to stop them sometimes + // getting pushed out into channels by more-free connectors + // to the "inner" side of them. + weight = strongWeight; + } + currSegment->variable = new Variable(varID, idealPos, weight); + vs.push_back(currSegment->variable); + size_t index = vs.size() - 1; + //printf("line %.15f pos: %g min: %g max: %g\n", + // lowPt[dimension], idealPos, currSegment->minSpaceLimit, + // currSegment->maxSpaceLimit); + + // Constrain position in relation to previously seen segments, + // if necessary (i.e. when they could overlap). + for (ShiftSegmentPtrList::iterator prevVarIt = prevVars.begin(); + prevVarIt != prevVars.end(); ) + { + ShiftSegment *prevSeg = *prevVarIt; + Variable *prevVar = prevSeg->variable; + + if (currSegment->overlapsWith(*prevSeg, dimension) && + (!(currSegment->fixed) || !(prevSeg->fixed))) + { + // If there is a previous segment to the left that + // could overlap this in the shift direction, then + // constrain the two segments to be separated. + // Though don't add the constraint if both the + // segments are fixed in place. + cs.push_back(new Constraint(prevVar, vs[index], + router->orthogonalNudgeDistance())); + prevVarIt = prevVars.erase(prevVarIt); + } + else + { + ++prevVarIt; + } + } + + if (!currSegment->fixed) + { + // If this segment sees a channel boundary to its left, + // then constrain its placement as such. + if (currSegment->minSpaceLimit > -CHANNEL_MAX) + { + vs.push_back(new Variable(fixedID, + currSegment->minSpaceLimit, fixedWeight)); + cs.push_back(new Constraint(vs[vs.size() - 1], vs[index], + 0.0)); + } + + // If this segment sees a channel boundary to its right, + // then constrain its placement as such. + if (currSegment->maxSpaceLimit < CHANNEL_MAX) + { + vs.push_back(new Variable(fixedID, + currSegment->maxSpaceLimit, fixedWeight)); + cs.push_back(new Constraint(vs[index], vs[vs.size() - 1], + 0.0)); + } + } + prevVars.push_back(&(*currSegment)); + } +#if 0 + for(unsigned i=0;i<vs.size();i++) { + printf("-vs[%d]=%f\n",i,vs[i]->desiredPosition); + } +#endif + IncSolver f(vs,cs); + f.solve(); + bool satisfied = true; + for (size_t i = 0; i < vs.size(); ++i) + { + if (vs[i]->id == fixedID) + { + if (fabs(vs[i]->finalPosition - vs[i]->desiredPosition) > 0.01) + { + satisfied = false; + break; + } + } + } + if (satisfied) + { + for (ShiftSegmentList::iterator currSegment = currentRegion.begin(); + currSegment != currentRegion.end(); ++currSegment) + { + Point& lowPt = currSegment->lowPoint(); + Point& highPt = currSegment->highPoint(); + double newPos = currSegment->variable->finalPosition; + //printf("Pos: %X, %g\n", (int) currSegment->connRef, newPos); + lowPt[dimension] = newPos; + highPt[dimension] = newPos; + } + } +#if 0 + for(unsigned i=0;i<vs.size();i++) { + printf("+vs[%d]=%f\n",i,vs[i]->finalPosition); + } +#endif + for_each(vs.begin(),vs.end(),delete_object()); + for_each(cs.begin(),cs.end(),delete_object()); + } +} + + +extern void improveOrthogonalRoutes(Router *router) +{ + router->timers.Register(tmOrthogNudge, timerStart); + for (size_t dimension = 0; dimension < 2; ++dimension) + { + // Build nudging info. + // XXX: We need to build the point orders separately in each + // dimension since things move. There is probably a more + // efficient way to do this. + PtOrderMap pointOrders; + buildOrthogonalNudgingOrderInfo(router, pointOrders); + + // Simplify routes. + simplifyOrthogonalRoutes(router); + + // Do the centring and nudging. + ShiftSegmentList segLists; + buildOrthogonalChannelInfo(router, dimension, segLists); + nudgeOrthogonalRoutes(router, dimension, pointOrders, segLists); + } + router->timers.Stop(); +} + + +} diff --git a/src/libavoid/orthogonal.h b/src/libavoid/orthogonal.h new file mode 100644 index 000000000..4fb974099 --- /dev/null +++ b/src/libavoid/orthogonal.h @@ -0,0 +1,39 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + + +#ifndef AVOID_ORTHOGONAL_H +#define AVOID_ORTHOGONAL_H + +namespace Avoid { + + +extern void generateStaticOrthogonalVisGraph(Router *router); + +extern void improveOrthogonalRoutes(Router *router); + + +} + +#endif diff --git a/src/libavoid/polyutil.cpp b/src/libavoid/polyutil.cpp deleted file mode 100644 index 1b4b0c619..000000000 --- a/src/libavoid/polyutil.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - -#include <cassert> -#include <cstdlib> - -#include "libavoid/polyutil.h" -#include "libavoid/geomtypes.h" -#include "libavoid/vertices.h" -#include "libavoid/shape.h" - -namespace Avoid { - - -Polygn newPoly(int size) -{ - Polygn newpoly; - - newpoly.id = 0; - newpoly.pn = size; - newpoly.ps = (Point *) calloc(size, sizeof(Point)); - if (!newpoly.ps) - { - fprintf(stderr, - "Error: Unable to allocate Point array in Avoid::newPoly\n"); - abort(); - } - return newpoly; -} - - -Polygn copyPoly(Polygn poly) -{ - Polygn newpoly = newPoly(poly.pn); - - newpoly.id = poly.id; - for (int i = 0; i < poly.pn; i++) - { - newpoly.ps[i] = poly.ps[i]; - } - return newpoly; -} - - -Polygn copyPoly(ShapeRef *shape) -{ - Polygn poly = shape->poly(); - Polygn newpoly = newPoly(poly.pn); - - newpoly.id = poly.id; - for (int i = 0; i < poly.pn; i++) - { - newpoly.ps[i] = poly.ps[i]; - } - return newpoly; -} - - -void freePoly(Polygn& poly) -{ - std::free(poly.ps); -} - - -void freePtrPoly(Polygn *poly) -{ - std::free(poly->ps); - std::free(poly); -} - - -} - diff --git a/src/libavoid/polyutil.h b/src/libavoid/polyutil.h deleted file mode 100644 index 9340df5f4..000000000 --- a/src/libavoid/polyutil.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - -#include "libavoid/geometry.h" -#include "libavoid/shape.h" - -#ifndef AVOID_POLYUTIL_H -#define AVOID_POLYUTIL_H - -namespace Avoid { - - -extern Polygn newPoly(int size); -extern Polygn copyPoly(Polygn); -extern Polygn copyPoly(ShapeRef *shape); -extern void freePoly(Polygn&); -extern void freePtrPoly(Polygn *argpoly); - - -} - -#endif diff --git a/src/libavoid/region.cpp b/src/libavoid/region.cpp deleted file mode 100644 index 5a46d7cbb..000000000 --- a/src/libavoid/region.cpp +++ /dev/null @@ -1,858 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - -#include <stdio.h> -#include <cstdlib> -#include <algorithm> -#include <cassert> -#include <cmath> - -#include "libavoid/shape.h" -#include "libavoid/region.h" - - -namespace Avoid { - -Region *centerRegion = NULL; - -static const BBox screenBBox = - {Point(-INFINITY, -INFINITY), Point(INFINITY, INFINITY)}; - - -Region::Region() - : _bbox(screenBBox) - , _left(NULL), _right(NULL), _up(NULL), _down(NULL) -{ - _blocks.clear(); -} - - -Region::Region(double x1, double y1, double x2, double y2) - : _left(NULL), _right(NULL), _up(NULL), _down(NULL) -{ - _bbox.a.x = x1; - _bbox.a.y = y1; - _bbox.b.x = x2; - _bbox.b.y = y2; - - _blocks.clear(); -} - - -static const unsigned int R_INSIDE = 0; -static const unsigned int R_LEFT = 1; -static const unsigned int R_LEDGE = 2; -static const unsigned int R_RIGHT = 4; -static const unsigned int R_REDGE = 8; -static const unsigned int R_ABOVE = 16; -static const unsigned int R_TEDGE = 32; -static const unsigned int R_BELOW = 64; -static const unsigned int R_BEDGE = 128; - -static const unsigned int R_NONE = R_INSIDE; -static const unsigned int R_UP = R_ABOVE; -static const unsigned int R_DOWN = R_BELOW; -static const unsigned int R_HORI = R_LEFT | R_RIGHT; -static const unsigned int R_VERT = R_UP | R_DOWN; - - -static void printBBox(const char *label, const BBox &bbox) -{ - if (label) - { - printf("%s: ", label); - } - printf("(%.2f, %.2f)-(%.2f, %.2f)\n", bbox.a.x, bbox.a.y, - bbox.b.x, bbox.b.y); -} - - -bool Region::overlapCheck(BBox& bbox, unsigned int& p) -{ - p = R_INSIDE; - Point& a = bbox.a; - Point& b = bbox.b; - Point& r = _bbox.a; - Point& s = _bbox.b; - - if (s.x <= a.x) - { - // Wholly right. - p = R_RIGHT; - return false; - } - else if (r.x >= b.x) - { - // Wholly left. - p = R_LEFT; - return false; - } - - if (s.y <= a.y) - { - // Wholly below. - p = R_BELOW; - return false; - } - else if (r.y >= b.y) - { - // Wholly above. - p = R_ABOVE; - return false; - } - - if (a.y == r.y) - { - // Shared top edge. - p |= R_TEDGE; - } - else if (a.y < r.y) - { - // Need to split above. - p |= R_ABOVE; - } - - if (b.y == s.y) - { - // Shared bottom edge. - p |= R_BEDGE; - } - else if (b.y > s.y) - { - // Need to split below. - p |= R_BELOW; - } - - if (a.x == r.x) - { - // Shared left edge. - p |= R_LEDGE; - } - else if (a.x < r.x) - { - // Need to split left. - p |= R_LEFT; - } - - if (b.x == s.x) - { - // Shared right edge. - p |= R_REDGE; - } - else if (b.x > s.x) - { - // Need to split right. - p |= R_RIGHT; - } - - return true; -} - - -void Region::getBBox(BBox& bb) -{ - bb.a = _bbox.a; - bb.b = _bbox.b; -} - - -Region *Region::up(void) -{ - return _up; -} - - -Region *Region::down(void) -{ - return _down; -} - - -Region *Region::left(void) -{ - return _left; -} - - -Region *Region::right(void) -{ - return _right; -} - - -bool Region::isBlock(void) -{ - return !(_blocks.empty()); -} - - -void Region::initialSplit(BBox& bbox, unsigned int pos, unsigned int& shapeId) -{ - Point& n1 = bbox.a; - Point& n2 = bbox.b; - Region *newR = this; - Region *left, *right, *top, *bottom; - - if (pos == R_INSIDE) - { - split(n2.y, R_HORI); - split(n2.x, R_VERT); - newR = split(n1.y, R_HORI); - newR = newR->split(n1.x, R_VERT); - - printf("%p - list %d add %d\n", newR, - (int) newR->_blocks.size(), shapeId); - newR->_blocks.push_back((int) shapeId); - newR->_blocks.sort(); - newR->_blocks.unique(); - } - else - { - Region *tar = NULL; - tar = newR->findRegion(n1.x, R_VERT); - if (pos & R_LEFT) - { - if (n1.x == tar->_bbox.a.x) - { - newR = tar->_right; - } - else if (n1.x == tar->_bbox.b.x) - { - newR = tar; - } - else - { - newR = tar->split(n1.x, R_VERT); - } - } - else if (!(pos & R_LEDGE)) - { - newR = tar->split(n1.x, R_VERT); - } - left = newR; - - tar = left->findRegion(n1.y, R_HORI); - if (pos & R_ABOVE) - { - if (n1.y == tar->_bbox.a.y) - { - newR = tar->_down; - } - else if (n1.y == tar->_bbox.b.y) - { - newR = tar; - } - else - { - newR = tar->split(n1.y, R_HORI); - } - } - else if (!(pos & R_TEDGE)) - { - newR = tar->split(n1.y, R_HORI); - } - top = newR; - - right = newR; - tar = newR->findRegion(n2.x, R_VERT); - if (pos & R_RIGHT) - { - - if (n2.x == tar->_bbox.a.x) - { - right = tar->_left; - } - else if (n2.x == tar->_bbox.b.x) - { - right = tar; - } - else - { - tar->split(n2.x, R_VERT); - right = tar; - } - } - else if (!(pos & R_REDGE)) - { - tar->split(n2.x, R_VERT); - right = tar; - } - - bottom = right; - tar = right->findRegion(n2.y, R_HORI); - if (pos & R_BELOW) - { - if (n2.y == tar->_bbox.a.y) - { - bottom = tar->_up; - } - else if (n2.y == tar->_bbox.b.y) - { - bottom = tar; - } - else - { - tar->split(n2.y, R_HORI); - bottom = tar; - } - } - else if (!(pos & R_BEDGE)) - { - tar->split(n2.y, R_HORI); - bottom = tar; - } - - // top is actually top-left, and bottom is bottom-right. - Region *curr = top, *cptr = NULL; - while (curr->_bbox.b.y <= bottom->_bbox.b.y) - { - cptr = curr; - while (cptr->_bbox.b.x <= bottom->_bbox.b.x) - { - printf("%p - list %d add %d\n", cptr, - (int) cptr->_blocks.size(), shapeId); - cptr->_blocks.push_back((int) shapeId); - cptr->_blocks.sort(); - cptr->_blocks.unique(); - - cptr = cptr->_right; - } - - curr = curr->_down; - } - } -} - - -// Returns the region containing the value 'pos' in the direction 'dir'. -// Thus, if looking for the x value 55, you would pass R_VERT as 'dir'. -// 'forMerge' specifies that the left or top block of a pair of regions -// with the split value of 'pos' should be returned. -Region *Region::findRegion(double pos, unsigned int dir, const bool forMerge) -{ - Region *curr = this; - - if (dir & R_VERT) - { - while (pos > curr->_bbox.b.x) - { - curr = curr->_right; - } - while (pos < curr->_bbox.a.x) - { - curr = curr->_left; - } - if (forMerge) - { - if (pos == curr->_bbox.a.x) - { - curr = curr->_left; - } - if (pos != curr->_bbox.b.x) - { - // 'pos' is not on the boundary. - return NULL; - } - } - } - else if (dir & R_HORI) - { - while (pos > curr->_bbox.b.y) - { - curr = curr->_down; - } - while (pos < curr->_bbox.a.y) - { - curr = curr->_up; - } - if (forMerge) - { - if (pos == curr->_bbox.a.y) - { - curr = curr->_up; - } - if (pos != curr->_bbox.b.y) - { - // 'pos' is not on the boundary. - return NULL; - } - } - } - return curr; -} - - -Region *Region::split(double pos, unsigned int dir) -{ - Region *newR = NULL; - bool first = true; - - if (dir & R_VERT) - { - newR = splitDir(pos, R_UP, first); - if (_down) _down->splitDir(pos, R_DOWN); - } - else if (dir & R_HORI) - { - newR = splitDir(pos, R_RIGHT, first); - if (_left) _left->splitDir(pos, R_LEFT); - } - return newR; -} - - -void Region::merge(unsigned int dir) -{ - bool first = true; - - if (dir & R_VERT) - { - mergeDir(R_UP, first); - if (_down) _down->mergeDir(R_DOWN); - } - else if (dir & R_HORI) - { - mergeDir(R_RIGHT, first); - if (_left) _left->mergeDir(R_LEFT); - } -} - - -void Region::mergeRegion(Region *src) -{ - assert(src != NULL); - - if (src == _left) - { - pairHor(src->_left, this); - _bbox.a.x = src->_bbox.a.x; - } - else if (src == _right) - { - pairHor(this, src->_right); - _bbox.b.x = src->_bbox.b.x; - } - else if (src == _up) - { - pairVer(src->_up, this); - _bbox.a.y = src->_bbox.a.y; - } - else if (src == _down) - { - pairVer(this, src->_down); - _bbox.b.y = src->_bbox.b.y; - } - else - { - fprintf(stderr, "Error: Avoid::Region::merge(): " - "Argument not adjoining region.\n"); - abort(); - } - mergeAttributes(src); - printf("DEL %p\n", src); - delete src; -} - - -Region *Region::splitDir(double pos, unsigned int dir, bool first) -{ - Point& o1 = _bbox.a; - Point& o2 = _bbox.b; - - Region *newR = NULL; - - if (dir & R_VERT) - { - assert(pos > _bbox.a.x); - assert(pos < _bbox.b.x); - - // Vertical recursion: - - // Create new block. - Region *r = new Region(pos, o1.y, o2.x, o2.y); - printf("NEW %p\n", r); - r->copyAttributes(this); - - Region *o_up = _up; - Region *o_down = _down; - - // Resize old block. - o2.x = pos; - - pairHor(r, _right); - pairHor(this, r); - - if (dir & R_UP) - { - if (!first) pairVer(r, _down->_right); - - if (o_up) o_up->splitDir(pos, R_UP); - } - else if (dir & R_DOWN) - { - if (!first) pairVer(_up->_right, r); - - if (o_down) o_down->splitDir(pos, R_DOWN); - } - newR = r; - } - else if (dir & R_HORI) - { - // Vertical recursion: - - // Create new block. - Region *b = new Region(o1.x, pos, o2.x, o2.y); - printf("NEW %p\n", b); - b->copyAttributes(this); - - Region *o_left = _left; - Region *o_right = _right; - - // Resize old block. - o2.y = pos; - - pairVer(b, _down); - pairVer(this, b); - - if (dir & R_LEFT) - { - if (!first) pairHor(b, _right->_down); - - if (o_left) o_left->splitDir(pos, R_LEFT); - } - else if (dir & R_RIGHT) - { - if (!first) pairHor(_left->_down, b); - - if (o_right) o_right->splitDir(pos, R_RIGHT); - } - newR = b; - } - return newR; -} - - -void Region::mergeDir(unsigned int dir, bool first) -{ - if (dir & R_VERT) - { - assert(_right != NULL); - - mergeRegion(_right); - - if (dir & R_UP) - { - if (_up) _up->mergeDir(dir, R_UP); - } - else if (dir & R_DOWN) - { - if (_down) _down->mergeDir(dir, R_DOWN); - } - } - else if (dir & R_HORI) - { - assert(_down != NULL); - - mergeRegion(_down); - - if (dir & R_LEFT) - { - if (_left) _left->mergeDir(dir, R_LEFT); - } - else if (dir & R_RIGHT) - { - if (_right) _right->mergeDir(dir, R_RIGHT); - } - } -} - - -void Region::copyAttributes(Region *src) -{ - _blocks = src->_blocks; -} - - -void Region::mergeAttributes(Region *src) -{ - _blocks.merge(src->_blocks); - _blocks.sort(); - _blocks.unique(); -} - - -//--------------------------- -// Static member functions: - - -void Region::pairHor(Region *l, Region *r) -{ - if (l) l->_right = r; - if (r) r->_left = l; -} - - -void Region::pairVer(Region *a, Region *b) -{ - if (a) a->_down = b; - if (b) b->_up = a; -} - - -void Region::addShape(ShapeRef *shape) -{ - if (!centerRegion) - { - // Add new default region. - centerRegion = new Region(); - printf("NEW %p\n", centerRegion); - } - BBox bbox; - // Get bounding box for added shape. - shape->boundingBox(bbox); - printBBox("Add", bbox); - - unsigned int shapeId = shape->id(); - - // Find starting point for overlap - Region *curr = centerRegion; - unsigned int pos = R_INSIDE; - while (!(curr->overlapCheck(bbox, pos))) - { - if (pos & R_LEFT) - { - curr = curr->_left; - } - else if (pos & R_RIGHT) - { - curr = curr->_right; - } - else if (pos & R_ABOVE) - { - curr = curr->_up; - } - else if (pos & R_BELOW) - { - curr = curr->_down; - } - } - - curr->initialSplit(bbox, pos, shapeId); - centerRegion = curr; -} - - -void Region::removeShape(ShapeRef *shape) -{ - const bool forMerge = true; - - BBox bbox; - // Get bounding box for added shape. - shape->boundingBox(bbox); - printBBox("Remove", bbox); - - unsigned int shapeId = shape->id(); - - Region *aboveTop = centerRegion->findRegion(bbox.a.y, R_HORI, forMerge); - Region *aboveBottom = aboveTop->findRegion(bbox.b.y, R_HORI, forMerge); - Region *leftOfLeft = aboveBottom->findRegion(bbox.a.x, R_VERT, forMerge); - Region *leftOfRight = leftOfLeft->findRegion(bbox.b.x, R_VERT, forMerge); - - assert(aboveTop != NULL); - assert(aboveBottom != NULL); - assert(leftOfLeft != NULL); - assert(leftOfRight != NULL); - - // Find Top Left. - Region *topLeft = leftOfLeft->_right; - while (topLeft->_bbox.a.y != aboveTop->_bbox.b.y) - { - topLeft = topLeft->_up; - } - - // Find Bottom Right. - Region *botRight = leftOfRight; - while (botRight->_bbox.b.y != aboveBottom->_bbox.b.y) - { - botRight = botRight->_down; - } - - // Clear blocking flag. - Region *curr = topLeft, *cptr = NULL; - while (curr->_bbox.b.y <= botRight->_bbox.b.y) - { - cptr = curr; - while (cptr->_bbox.b.x <= botRight->_bbox.b.x) - { - ShapeList& blocks = cptr->_blocks; - - assert(std::find(blocks.begin(), blocks.end(), (int) shapeId) != - blocks.end()); - printf("%p - list %d remove %d\n", cptr, - (int) blocks.size(), shapeId); - cptr->_blocks.remove((int) shapeId); - - cptr = cptr->_right; - } - - curr = curr->_down; - } - - // These two are safe since they don't invalidate the pointers - // to the regions that are inside the shape. - if (aboveBottom->safeToMerge(R_HORI)) - { - aboveBottom->merge(R_HORI); - } - if (leftOfRight->safeToMerge(R_VERT)) - { - leftOfRight->merge(R_VERT); - } - - // Be a bit more careful with these. - double leftX = leftOfLeft->_bbox.b.x; - if (aboveTop->safeToMerge(R_HORI)) - { - aboveTop->merge(R_HORI); - } - // leftOfLeft may have been freed, so look for the new block at - // that position. - leftOfLeft = aboveTop->findRegion(leftX, R_VERT, forMerge); - assert(leftOfLeft); - if (leftOfLeft->safeToMerge(R_VERT)) - { - leftOfLeft->merge(R_VERT); - } -} - - -bool Region::safeToMerge(unsigned int dir) -{ - bool unsafe = false; - - if (dir == R_HORI) - { - printf("safeToMerge? y = %.3f... ", _bbox.b.y); - unsafe |= unsafeToMerge(R_RIGHT); - if (_left) unsafe |= _left->unsafeToMerge(R_LEFT); - } - else if (dir == R_VERT) - { - printf("safeToMerge? x = %.3f... ", _bbox.b.x); - unsafe |= unsafeToMerge(R_DOWN); - if (_up) unsafe |= _up->unsafeToMerge(R_UP); - } - printf("%s.\n", (unsafe) ? "no" : "yes"); - - return !unsafe; -} - - -bool Region::unsafeToMerge(unsigned int dir) -{ - bool unsafe = false; - - if (dir & R_HORI) - { - // If they are not the same on both sides then we can merge. - if (_blocks != _down->_blocks) - { - printf("\n _blocks:\n "); - for (ShapeList::iterator i = _blocks.begin(); i != _blocks.end(); - ++i) - { - printf("%d ", *i); - } - printf("\n _down->_blocks:\n "); - for (ShapeList::iterator i = _down->_blocks.begin(); - i != _down->_blocks.end(); ++i) - { - printf("%d ", *i); - } - unsafe |= true; - printf("\n"); - } - - if ((dir == R_LEFT) && _left) - { - unsafe |= _left->unsafeToMerge(dir); - } - else if ((dir == R_RIGHT) && _right) - { - unsafe |= _right->unsafeToMerge(dir); - } - } - else if (dir & R_VERT) - { - if (_blocks != _right->_blocks) - { - printf("\n _blocks:\n "); - for (ShapeList::iterator i = _blocks.begin(); i != _blocks.end(); - ++i) - { - printf("%d ", *i); - } - printf("\n _right->_blocks:\n "); - for (ShapeList::iterator i = _right->_blocks.begin(); - i != _right->_blocks.end(); ++i) - { - printf("%d ", *i); - } - unsafe |= true; - printf("\n"); - } - - if ((dir == R_UP) && _up) - { - unsafe |= _up->unsafeToMerge(dir); - } - else if ((dir == R_DOWN) && _down) - { - unsafe |= _down->unsafeToMerge(dir); - } - } - return unsafe; - -} - - -Region *Region::getTopLeftRegion(void) -{ - Region *curr = centerRegion; - - while (curr && (curr->_up || curr->_left)) - { - if (curr->_up) - { - curr = curr->_up; - } - else - { - curr = curr->_left; - } - } - return curr; -} - - -} - diff --git a/src/libavoid/region.h b/src/libavoid/region.h deleted file mode 100644 index 6a44ccb08..000000000 --- a/src/libavoid/region.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - -#ifndef AVOID_REGION_H -#define AVOID_REGION_H - -#include <list> -#include "libavoid/geomtypes.h" - -namespace Avoid { - -class ShapeRef; - -typedef std::list<int> ShapeList; - - -class Region -{ - public: - Region(); - Region(double x1, double y1, double x2, double y2); - - bool overlapCheck(BBox& bbox, unsigned int& p); - void getBBox(BBox& bb); - Region *up(void); - Region *down(void); - Region *left(void); - Region *right(void); - void initialSplit(BBox& bbox, unsigned int pos, unsigned int& shapeId); - bool isBlock(void); - - static void pairHor(Region *l, Region *r); - static void pairVer(Region *a, Region *b); - static void addShape(ShapeRef *shape); - static void removeShape(ShapeRef *shape); - static Region *getTopLeftRegion(void); - - private: - BBox _bbox; - Region *_left; - Region *_right; - Region *_up; - Region *_down; - ShapeList _blocks; - - Region *split(double pos, unsigned int dir); - void merge(unsigned int dir); - void mergeRegion(Region *src); - Region *findRegion(double pos, unsigned int dir, - const bool forMerge = false); - Region *splitDir(double pos, unsigned int dir, bool first = false); - void mergeDir(unsigned int dir, bool first = false); - void copyAttributes(Region *src); - void mergeAttributes(Region *src); - bool safeToMerge(unsigned int dir); - bool unsafeToMerge(unsigned int dir); -}; - - -} - -#endif diff --git a/src/libavoid/router.cpp b/src/libavoid/router.cpp index df0bacd02..ab13a981b 100644 --- a/src/libavoid/router.cpp +++ b/src/libavoid/router.cpp @@ -2,302 +2,667 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ -#include <cstdlib> + +#include <algorithm> +#include <cmath> + #include "libavoid/shape.h" #include "libavoid/router.h" #include "libavoid/visibility.h" #include "libavoid/connector.h" -#include "libavoid/polyutil.h" #include "libavoid/debug.h" -#include "libavoid/region.h" -#include "math.h" - -//#define ORTHOGONAL_ROUTING +#include "libavoid/orthogonal.h" +#include "libavoid/assertions.h" namespace Avoid { -static const unsigned int infoAdd = 1; -static const unsigned int infoDel = 2; -static const unsigned int infoMov = 3; +enum ActionType { + ShapeMove, + ShapeAdd, + ShapeRemove, + ConnChange +}; +typedef std::list<std::pair<unsigned int, ConnEnd> > ConnUpdateList; -class MoveInfo { +class ActionInfo { public: - MoveInfo(ShapeRef *s, Polygn *p, bool fM) - : shape(s) - , newPoly(copyPoly(*p)) - , firstMove(fM) - { } - ~MoveInfo() + ActionInfo(ActionType t, ShapeRef *s, const Polygon& p, bool fM) + : type(t), + objPtr(s), + newPoly(p), + firstMove(fM) + { + COLA_ASSERT(type == ShapeMove); + } + ActionInfo(ActionType t, ShapeRef *s) + : type(t), + objPtr(s) + { + COLA_ASSERT(type != ConnChange); + } + ActionInfo(ActionType t, ConnRef *c) + : type(t), + objPtr(c) + { + COLA_ASSERT(type == ConnChange); + } + ~ActionInfo() + { + } + ShapeRef *shape(void) const { - freePoly(newPoly); + COLA_ASSERT((type == ShapeMove) || (type == ShapeAdd) || + (type == ShapeRemove)); + return (static_cast<ShapeRef *> (objPtr)); } - ShapeRef *shape; - Polygn newPoly; + ConnRef *conn(void) const + { + COLA_ASSERT(type == ConnChange); + return (static_cast<ConnRef *> (objPtr)); + } + bool operator==(const ActionInfo& rhs) const + { + return (type == rhs.type) && (objPtr == rhs.objPtr); + } + bool operator<(const ActionInfo& rhs) const + { + if (type != rhs.type) + { + return type < rhs.type; + } + return objPtr < rhs.objPtr; + } + ActionType type; + void *objPtr; + Polygon newPoly; bool firstMove; + ConnUpdateList conns; }; - -Router::Router() - : PartialTime(false) - , SimpleRouting(false) - , segmt_penalty(0) - , angle_penalty(0) - , crossing_penalty(200) - // Algorithm options: - , UseAStarSearch(true) - , IgnoreRegions(true) - , SelectiveReroute(true) - , IncludeEndpoints(true) - , UseLeesAlgorithm(false) - , InvisibilityGrph(true) - , ConsolidateMoves(true) - , PartialFeedback(false) - // Instrumentation: - , st_checked_edges(0) -#ifdef LINEDEBUG - , avoid_screen(NULL) +Router::Router(const unsigned int flags) + : visOrthogGraph(true), + PartialTime(false), + SimpleRouting(false), + ClusteredRouting(true), + // Poly-line algorithm options: + IgnoreRegions(true), + UseLeesAlgorithm(true), + InvisibilityGrph(true), + // General algorithm options: + SelectiveReroute(true), + PartialFeedback(false), + RubberBandRouting(false), + // Instrumentation: + st_checked_edges(0), +#ifdef LIBAVOID_SDL + avoid_screen(NULL), #endif -{ } + _largestAssignedId(0), + _consolidateActions(true), + _orthogonalNudgeDistance(4.0), + // Mode options: + _polyLineRouting(false), + _orthogonalRouting(false), + _staticGraphInvalidated(true), + _inCrossingPenaltyReroutingStage(false) +{ + // At least one of the Routing modes must be set. + COLA_ASSERT(flags & (PolyLineRouting | OrthogonalRouting)); + if (flags & PolyLineRouting) + { + _polyLineRouting = true; + } + if (flags & OrthogonalRouting) + { + _orthogonalRouting = true; + } + for (size_t p = 0; p < lastPenaltyMarker; ++p) + { + _routingPenalties[p] = 0.0; + } + _routingPenalties[clusterCrossingPenalty] = 4000; +} -void Router::addShape(ShapeRef *shape) +Router::~Router() { - unsigned int pid = shape->id(); - Polygn poly = shape->poly(); + // Delete remaining connectors. + ConnRefList::iterator conn = connRefs.begin(); + while (conn != connRefs.end()) + { + db_printf("Deleting connector %u in ~Router()\n", (*conn)->id()); + delete *conn; + conn = connRefs.begin(); + } - adjustContainsWithAdd(poly, pid); - - // o Check all visibility edges to see if this one shape - // blocks them. - newBlockingShape(&poly, pid); + // Remove remaining shapes. + ShapeRefList::iterator shape = shapeRefs.begin(); + while (shape != shapeRefs.end()) + { + ShapeRef *shapePtr = *shape; + db_printf("Deleting shape %u in ~Router()\n", shapePtr->id()); + if (shapePtr->isActive()) + { + shapePtr->removeFromGraph(); + shapePtr->makeInactive(); + } + delete shapePtr; + shape = shapeRefs.begin(); + } -#ifdef ORTHOGONAL_ROUTING - Region::addShape(shape); -#endif + // Cleanup orphaned orthogonal graph vertices. + destroyOrthogonalVisGraph(); + + COLA_ASSERT(connRefs.size() == 0); + COLA_ASSERT(shapeRefs.size() == 0); + COLA_ASSERT(visGraph.size() == 0); + COLA_ASSERT(invisGraph.size() == 0); +} + + +void Router::modifyConnector(ConnRef *conn, const unsigned int type, + const ConnEnd& connEnd) +{ + ActionInfo modInfo(ConnChange, conn); - // o Calculate visibility for the new vertices. - if (UseLeesAlgorithm) + ActionInfoList::iterator found = + find(actionList.begin(), actionList.end(), modInfo); + if (found == actionList.end()) { - shapeVisSweep(shape); + modInfo.conns.push_back(std::make_pair(type, connEnd)); + actionList.push_back(modInfo); } else { - shapeVis(shape); + found->conns.push_back(std::make_pair(type, connEnd)); + } + + if (!_consolidateActions) + { + processTransaction(); } - callbackAllInvalidConnectors(); } -void Router::delShape(ShapeRef *shape) +void Router::modifyConnector(ConnRef *conn) { - unsigned int pid = shape->id(); + ActionInfo modInfo(ConnChange, conn); - // Delete items that are queued in the movList. - for (MoveInfoList::iterator it = moveList.begin(); it != moveList.end(); ) + ActionInfoList::iterator found = + find(actionList.begin(), actionList.end(), modInfo); + if (found == actionList.end()) { - if ((*it)->shape->id() == pid) - { - MoveInfoList::iterator doomed = it; - ++it; - moveList.erase(doomed); - } - else - { - ++it; - } + actionList.push_back(modInfo); } - // o Remove entries related to this shape's vertices - shape->removeFromGraph(); - - if (SelectiveReroute) + if (!_consolidateActions) { - markConnectors(shape); + processTransaction(); } +} - adjustContainsWithDel(pid); - -#ifdef ORTHOGONAL_ROUTING - Region::removeShape(shape); -#endif - delete shape; - - // o Check all edges that were blocked by this shape. - if (InvisibilityGrph) +void Router::removeQueuedConnectorActions(ConnRef *conn) +{ + ActionInfo modInfo(ConnChange, conn); + + ActionInfoList::iterator found = + find(actionList.begin(), actionList.end(), modInfo); + if (found != actionList.end()) { - checkAllBlockedEdges(pid); + actionList.erase(found); } - else +} + + +void Router::addShape(ShapeRef *shape) +{ + // There shouldn't be remove events or move events for the same shape + // already in the action list. + // XXX: Possibly we could handle this by ordering them intelligently. + COLA_ASSERT(find(actionList.begin(), actionList.end(), + ActionInfo(ShapeRemove, shape)) == actionList.end()); + COLA_ASSERT(find(actionList.begin(), actionList.end(), + ActionInfo(ShapeMove, shape)) == actionList.end()); + + ActionInfo addInfo(ShapeAdd, shape); + + ActionInfoList::iterator found = + find(actionList.begin(), actionList.end(), addInfo); + if (found == actionList.end()) + { + actionList.push_back(addInfo); + } + + if (!_consolidateActions) + { + processTransaction(); + } +} + + +void Router::removeShape(ShapeRef *shape) +{ + // There shouldn't be add events events for the same shape already + // in the action list. + // XXX: Possibly we could handle this by ordering them intelligently. + COLA_ASSERT(find(actionList.begin(), actionList.end(), + ActionInfo(ShapeAdd, shape)) == actionList.end()); + + // Delete any ShapeMove entries for this shape in the action list. + ActionInfoList::iterator found = find(actionList.begin(), + actionList.end(), ActionInfo(ShapeMove, shape)); + if (found != actionList.end()) + { + actionList.erase(found); + } + + // Add the ShapeRemove entry. + ActionInfo remInfo(ShapeRemove, shape); + found = find(actionList.begin(), actionList.end(), remInfo); + if (found == actionList.end()) + { + actionList.push_back(remInfo); + } + + if (!_consolidateActions) { - // check all edges not in graph - checkAllMissingEdges(); + processTransaction(); } - callbackAllInvalidConnectors(); } -void Router::moveShape(ShapeRef *shape, Polygn *newPoly, const bool first_move) +void Router::moveShape(ShapeRef *shape, const double xDiff, const double yDiff) { + Polygon newPoly = shape->polygon(); + newPoly.translate(xDiff, yDiff); + + moveShape(shape, newPoly); +} + + +void Router::moveShape(ShapeRef *shape, const Polygon& newPoly, + const bool first_move) +{ + // There shouldn't be remove events or add events for the same shape + // already in the action list. + // XXX: Possibly we could handle this by ordering them intelligently. + COLA_ASSERT(find(actionList.begin(), actionList.end(), + ActionInfo(ShapeRemove, shape)) == actionList.end()); + + if (find(actionList.begin(), actionList.end(), + ActionInfo(ShapeAdd, shape)) != actionList.end()) + { + // The Add is enough, no need for the Move action too. + return; + } + + ActionInfo moveInfo(ShapeMove, shape, newPoly, first_move); // Sanely cope with the case where the user requests moving the same // shape multiple times before rerouting connectors. - bool alreadyThere = false; - unsigned int id = shape->id(); - MoveInfoList::iterator finish = moveList.end(); - for (MoveInfoList::iterator it = moveList.begin(); it != finish; ++it) + ActionInfoList::iterator found = + find(actionList.begin(), actionList.end(), moveInfo); + + if (found != actionList.end()) { - if ((*it)->shape->id() == id) + if (!SimpleRouting) { - if (!SimpleRouting) - { - fprintf(stderr, - "warning: multiple moves requested for shape %d.\n", - (int) id); - } - // Just update the MoveInfo with the second polygon, but - // leave the firstMove setting alone. - (*it)->newPoly = copyPoly(*newPoly); - alreadyThere = true; + db_printf("warning: multiple moves requested for shape %d " + "within a single transaction.\n", (int) shape->id()); } + // Just update the ActionInfo with the second polygon, but + // leave the firstMove setting alone. + found->newPoly = newPoly; + } + else + { + actionList.push_back(moveInfo); } - if (!alreadyThere) + if (!_consolidateActions) { - MoveInfo *moveInfo = new MoveInfo(shape, newPoly, first_move); - moveList.push_back(moveInfo); + processTransaction(); } +} + - if (!ConsolidateMoves) +void Router::setStaticGraphInvalidated(const bool invalidated) +{ + _staticGraphInvalidated = invalidated; +} + + +void Router::destroyOrthogonalVisGraph(void) +{ + // Remove orthogonal visibility graph edges. + visOrthogGraph.clear(); + + // Remove the now orphaned vertices. + VertInf *curr = vertices.shapesBegin(); + while (curr) { - processMoves(); + if (curr->orphaned() && (curr->id == dummyOrthogID)) + { + VertInf *following = vertices.removeVertex(curr); + delete curr; + curr = following; + continue; + } + curr = curr->lstNext; } } -void Router::processMoves(void) +void Router::regenerateStaticBuiltGraph(void) { - // If SimpleRouting, then don't update yet. - if (moveList.empty() || SimpleRouting) + // Here we do talks involved in updating the static-built visibility + // graph (if necessary) before we do any routing. + if (_staticGraphInvalidated) { - return; + if (_orthogonalRouting) + { + destroyOrthogonalVisGraph(); + + timers.Register(tmOrthogGraph, timerStart); + // Regenerate a new visibility graph. + generateStaticOrthogonalVisGraph(this); + + timers.Stop(); + } + _staticGraphInvalidated = false; + } +} + + +bool Router::shapeInQueuedActionList(ShapeRef *shape) const +{ + bool foundAdd = find(actionList.begin(), actionList.end(), + ActionInfo(ShapeAdd, shape)) != actionList.end(); + bool foundRem = find(actionList.begin(), actionList.end(), + ActionInfo(ShapeRemove, shape)) != actionList.end(); + bool foundMove = find(actionList.begin(), actionList.end(), + ActionInfo(ShapeMove, shape)) != actionList.end(); + + return (foundAdd || foundRem || foundMove); +} + + +bool Router::transactionUse(void) const +{ + return _consolidateActions; +} + + +void Router::setTransactionUse(const bool transactions) +{ + _consolidateActions = transactions; +} + + +bool Router::processTransaction(void) +{ + bool notPartialTime = !(PartialFeedback && PartialTime); + bool seenShapeMovesOrDeletes = false; + + // If SimpleRouting, then don't update here. + if (actionList.empty() || SimpleRouting) + { + return false; } - MoveInfoList::iterator curr; - MoveInfoList::iterator finish = moveList.end(); - for (curr = moveList.begin(); curr != finish; ++curr) + actionList.sort(); + ActionInfoList::iterator curr; + ActionInfoList::iterator finish = actionList.end(); + for (curr = actionList.begin(); curr != finish; ++curr) { - MoveInfo *moveInf = *curr; - ShapeRef *shape = moveInf->shape; - Polygn *newPoly = &(moveInf->newPoly); - bool first_move = moveInf->firstMove; + ActionInfo& actInf = *curr; + if (!((actInf.type == ShapeRemove) || (actInf.type == ShapeMove))) + { + // Not a move or remove action, so don't do anything. + continue; + } + seenShapeMovesOrDeletes = true; + + ShapeRef *shape = actInf.shape(); + bool isMove = (actInf.type == ShapeMove); + bool first_move = actInf.firstMove; unsigned int pid = shape->id(); - bool notPartialTime = !(PartialFeedback && PartialTime); // o Remove entries related to this shape's vertices shape->removeFromGraph(); - - if (SelectiveReroute && (notPartialTime || first_move)) + + if (SelectiveReroute && (!isMove || notPartialTime || first_move)) { markConnectors(shape); } adjustContainsWithDel(pid); - -#ifdef ORTHOGONAL_ROUTING - Region::removeShape(shape); -#endif - - shape->setNewPoly(*newPoly); - - adjustContainsWithAdd(*newPoly, pid); // Ignore this shape for visibility. // XXX: We don't really need to do this if we're not using Partial // Feedback. Without this the blocked edges still route // around the shape until it leaves the connector. shape->makeInactive(); - } - - if (InvisibilityGrph) + + if (seenShapeMovesOrDeletes && _polyLineRouting) { - for (curr = moveList.begin(); curr != finish; ++curr) + if (InvisibilityGrph) { - MoveInfo *moveInf = *curr; - ShapeRef *shape = moveInf->shape; - unsigned int pid = shape->id(); - - // o Check all edges that were blocked by this shape. - checkAllBlockedEdges(pid); + for (curr = actionList.begin(); curr != finish; ++curr) + { + ActionInfo& actInf = *curr; + if (!((actInf.type == ShapeRemove) || + (actInf.type == ShapeMove))) + { + // Not a move or remove action, so don't do anything. + continue; + } + + // o Check all edges that were blocked by this shape. + checkAllBlockedEdges(actInf.shape()->id()); + } + } + else + { + // check all edges not in graph + checkAllMissingEdges(); } - } - else - { - // check all edges not in graph - checkAllMissingEdges(); } - while ( ! moveList.empty() ) + for (curr = actionList.begin(); curr != finish; ++curr) { - MoveInfo *moveInf = moveList.front(); - ShapeRef *shape = moveInf->shape; - Polygn *newPoly = &(moveInf->newPoly); + ActionInfo& actInf = *curr; + if (!((actInf.type == ShapeAdd) || (actInf.type == ShapeMove))) + { + // Not a move or add action, so don't do anything. + continue; + } + + ShapeRef *shape = actInf.shape(); + Polygon& newPoly = actInf.newPoly; + bool isMove = (actInf.type == ShapeMove); unsigned int pid = shape->id(); - bool notPartialTime = !(PartialFeedback && PartialTime); // Restore this shape for visibility. shape->makeActive(); -#ifdef ORTHOGONAL_ROUTING - Region::addShape(shape); -#endif + if (isMove) + { + shape->setNewPoly(newPoly); + } + const Polygon& shapePoly = shape->polygon(); - // o Check all visibility edges to see if this one shape - // blocks them. - if (notPartialTime) + adjustContainsWithAdd(shapePoly, pid); + + if (_polyLineRouting) { - newBlockingShape(newPoly, pid); + // o Check all visibility edges to see if this one shape + // blocks them. + if (!isMove || notPartialTime) + { + newBlockingShape(shapePoly, pid); + } + + // o Calculate visibility for the new vertices. + if (UseLeesAlgorithm) + { + shapeVisSweep(shape); + } + else + { + shapeVis(shape); + } } + } - // o Calculate visibility for the new vertices. - if (UseLeesAlgorithm) + // Update connector endpoints. + for (curr = actionList.begin(); curr != finish; ++curr) + { + ActionInfo& actInf = *curr; + if (actInf.type != ConnChange) { - shapeVisSweep(shape); + continue; } - else + for (ConnUpdateList::iterator conn = actInf.conns.begin(); + conn != actInf.conns.end(); ++conn) { - shapeVis(shape); + actInf.conn()->updateEndPoint(conn->first, conn->second); } - - moveList.pop_front(); - delete moveInf; } - callbackAllInvalidConnectors(); + // Clear the actionList. + actionList.clear(); + + _staticGraphInvalidated = true; + rerouteAndCallbackConnectors(); + + return true; +} + + +void Router::addCluster(ClusterRef *cluster) +{ + cluster->makeActive(); + + unsigned int pid = cluster->id(); + ReferencingPolygon& poly = cluster->polygon(); + + adjustClustersWithAdd(poly, pid); +} + + +void Router::delCluster(ClusterRef *cluster) +{ + cluster->makeInactive(); + + unsigned int pid = cluster->id(); + + adjustClustersWithDel(pid); +} + + +void Router::setOrthogonalNudgeDistance(const double dist) +{ + COLA_ASSERT(dist >= 0); + _orthogonalNudgeDistance = dist; +} + + +double Router::orthogonalNudgeDistance(void) const +{ + return _orthogonalNudgeDistance; +} + + +unsigned int Router::assignId(const unsigned int suggestedId) +{ + // If the suggestedId is zero, then we assign the object the next + // smallest unassigned ID, otherwise we trust the ID given is unique. + unsigned int assignedId = (suggestedId == 0) ? + (_largestAssignedId + 1) : suggestedId; + + // Have the router record if this ID is larger than the _largestAssignedId. + _largestAssignedId = std::max(_largestAssignedId, assignedId); + + // If assertions are enabled, then we check that this ID really is unique. + COLA_ASSERT(idIsUnique(assignedId)); + + return assignedId; +} + + + // Returns whether the given ID is unique among all objects known by the + // router. Outputs a warning if the ID is found ore than once. + // It is expected this is only going to be called from assertions while + // debugging, so efficiency is not an issue and we just iterate over all + // objects. +bool Router::idIsUnique(const unsigned int id) const +{ + unsigned int count = 0; + + // Examine shapes. + for (ShapeRefList::const_iterator i = shapeRefs.begin(); + i != shapeRefs.end(); ++i) + { + if ((*i)->id() == id) + { + count++; + } + } + + // Examine connectors. + for (ConnRefList::const_iterator i = connRefs.begin(); + i != connRefs.end(); ++i) + { + if ((*i)->id() == id) + { + count++; + } + } + + // Examine clusters. + for (ClusterRefList::const_iterator i = clusterRefs.begin(); + i != clusterRefs.end(); ++i) + { + if ((*i)->id() == id) + { + count++; + } + } + + if (count > 1) + { + db_printf("Warning:\tlibavoid object ID %d not unique.\n", id); + return false; + } + return true; } @@ -313,13 +678,15 @@ void Router::processMoves(void) void Router::attachedConns(IntList &conns, const unsigned int shapeId, const unsigned int type) { - ConnRefList::iterator fin = connRefs.end(); - for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) { - - if ((type & runningTo) && ((*i)->_dstId == shapeId)) { + ConnRefList::const_iterator fin = connRefs.end(); + for (ConnRefList::const_iterator i = connRefs.begin(); i != fin; ++i) + { + if ((type & runningTo) && ((*i)->_dstId == shapeId)) + { conns.push_back((*i)->_id); } - else if ((type & runningFrom) && ((*i)->_srcId == shapeId)) { + else if ((type & runningFrom) && ((*i)->_srcId == shapeId)) + { conns.push_back((*i)->_id); } } @@ -331,16 +698,19 @@ void Router::attachedConns(IntList &conns, const unsigned int shapeId, void Router::attachedShapes(IntList &shapes, const unsigned int shapeId, const unsigned int type) { - ConnRefList::iterator fin = connRefs.end(); - for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) { - if ((type & runningTo) && ((*i)->_dstId == shapeId)) { + ConnRefList::const_iterator fin = connRefs.end(); + for (ConnRefList::const_iterator i = connRefs.begin(); i != fin; ++i) + { + if ((type & runningTo) && ((*i)->_dstId == shapeId)) + { if ((*i)->_srcId != 0) { // Only if there is a shape attached to the other end. shapes.push_back((*i)->_srcId); } } - else if ((type & runningFrom) && ((*i)->_srcId == shapeId)) { + else if ((type & runningFrom) && ((*i)->_srcId == shapeId)) + { if ((*i)->_dstId != 0) { // Only if there is a shape attached to the other end. @@ -351,18 +721,127 @@ void Router::attachedShapes(IntList &shapes, const unsigned int shapeId, } - // It's intended this function is called after shape movement has - // happened to alert connectors that they need to be rerouted. -void Router::callbackAllInvalidConnectors(void) + // It's intended this function is called after visibility changes + // resulting from shape movement have happened. It will alert + // rerouted connectors (via a callback) that they need to be redrawn. +void Router::rerouteAndCallbackConnectors(void) +{ + std::set<ConnRef *> reroutedConns; + ConnRefList::const_iterator fin = connRefs.end(); + + // Updating the orthogonal visibility graph if necessary. + regenerateStaticBuiltGraph(); + + timers.Register(tmOrthogRoute, timerStart); + for (ConnRefList::const_iterator i = connRefs.begin(); i != fin; ++i) + { + (*i)->_needs_repaint = false; + bool rerouted = (*i)->generatePath(); + if (rerouted) + { + reroutedConns.insert(*i); + } + } + timers.Stop(); + + // Find and reroute crossing connectors if crossing penalties are set. + improveCrossings(); + + // Perform centring and nudging for othogonal routes. + improveOrthogonalRoutes(this); + + // Alert connectors that they need redrawing. + for (ConnRefList::const_iterator i = connRefs.begin(); i != fin; ++i) + { + (*i)->_needs_repaint = true; + (*i)->performCallback(); + } +} + + +typedef std::set<ConnRef *> ConnRefSet; + +void Router::improveCrossings(void) { + const double crossing_penalty = routingPenalty(crossingPenalty); + const double shared_path_penalty = routingPenalty(fixedSharedPathPenalty); + if ((crossing_penalty == 0) && (shared_path_penalty == 0)) + { + // No penalties, return. + return; + } + + // Find crossings and reroute connectors. + _inCrossingPenaltyReroutingStage = true; + ConnRefSet crossingConns; ConnRefList::iterator fin = connRefs.end(); - for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) { - (*i)->handleInvalid(); + for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) + { + Avoid::Polygon& iRoute = (*i)->routeRef(); + ConnRefList::iterator j = i; + for (++j; j != fin; ++j) + { + if ((crossingConns.find(*i) != crossingConns.end()) && + (crossingConns.find(*j) != crossingConns.end())) + { + // We already know both these have crossings. + continue; + } + // Determine if this pair cross. + Avoid::Polygon& jRoute = (*j)->routeRef(); + CrossingsInfoPair crossingInfo = std::make_pair(0, 0); + bool meetsPenaltyCriteria = false; + for (size_t jInd = 1; jInd < jRoute.size(); ++jInd) + { + const bool finalSegment = ((jInd + 1) == jRoute.size()); + CrossingsInfoPair crossingInfo = countRealCrossings( + iRoute, true, jRoute, jInd, false, + finalSegment, NULL, NULL, *i, *j); + + if ((shared_path_penalty > 0) && + (crossingInfo.second & CROSSING_SHARES_PATH) && + (crossingInfo.second & CROSSING_SHARES_FIXED_SEGMENT) && + !(crossingInfo.second & CROSSING_SHARES_PATH_AT_END)) + { + // We are penalising fixedSharedPaths and there is a + // fixedSharedPath. + meetsPenaltyCriteria = true; + break; + } + else if ((crossing_penalty > 0) && (crossingInfo.first > 0)) + { + // We are penalising crossings and this is a crossing. + meetsPenaltyCriteria = true; + break; + } + } + if (meetsPenaltyCriteria) + { + crossingConns.insert(*i); + crossingConns.insert(*j); + } + } + } + + for (ConnRefSet::iterator i = crossingConns.begin(); + i != crossingConns.end(); ++i) + { + ConnRef *conn = *i; + conn->makePathInvalid(); + // XXX: Could we free these routes here for extra savings? + // conn->freeRoutes(); } + for (ConnRefSet::iterator i = crossingConns.begin(); + i != crossingConns.end(); ++i) + { + ConnRef *conn = *i; + conn->generatePath(); + } + _inCrossingPenaltyReroutingStage = false; } -void Router::newBlockingShape(Polygn *poly, int pid) +void Router::newBlockingShape(const Polygon& poly, int pid) { // o Check all visibility edges to see if this one shape // blocks them. @@ -382,8 +861,11 @@ void Router::newBlockingShape(Polygn *poly, int pid) Point e2 = points.second; bool blocked = false; - bool ep_in_poly1 = !(eID1.isShape) ? inPoly(*poly, e1) : false; - bool ep_in_poly2 = !(eID2.isShape) ? inPoly(*poly, e2) : false; + bool countBorder = false; + bool ep_in_poly1 = !(eID1.isShape) ? + inPoly(poly, e1, countBorder) : false; + bool ep_in_poly2 = !(eID2.isShape) ? + inPoly(poly, e2, countBorder) : false; if (ep_in_poly1 || ep_in_poly2) { // Don't check edges that have a connector endpoint @@ -391,10 +873,14 @@ void Router::newBlockingShape(Polygn *poly, int pid) continue; } - for (int pt_i = 0; pt_i < poly->pn; pt_i++) + bool seenIntersectionAtEndpoint = false; + for (size_t pt_i = 0; pt_i < poly.size(); ++pt_i) { - int pt_n = (pt_i == (poly->pn - 1)) ? 0 : pt_i + 1; - if (segmentIntersect(e1, e2, poly->ps[pt_i], poly->ps[pt_n])) + size_t pt_n = (pt_i == (poly.size() - 1)) ? 0 : pt_i + 1; + const Point& pi = poly.ps[pt_i]; + const Point& pn = poly.ps[pt_n]; + if (segmentShapeIntersect(e1, e2, pi, pn, + seenIntersectionAtEndpoint)) { blocked = true; break; @@ -422,7 +908,7 @@ void Router::newBlockingShape(Polygn *poly, int pid) void Router::checkAllBlockedEdges(int pid) { - assert(InvisibilityGrph); + COLA_ASSERT(InvisibilityGrph); for (EdgeInf *iter = invisGraph.begin(); iter != invisGraph.end() ; ) { @@ -444,18 +930,9 @@ void Router::checkAllBlockedEdges(int pid) void Router::checkAllMissingEdges(void) { - assert(!InvisibilityGrph); + COLA_ASSERT(!InvisibilityGrph); - VertInf *first = NULL; - - if (IncludeEndpoints) - { - first = vertices.connsBegin(); - } - else - { - first = vertices.shapesBegin(); - } + VertInf *first = vertices.connsBegin(); VertInf *pend = vertices.end(); for (VertInf *i = first; i != pend; i = i->lstNext) @@ -489,39 +966,79 @@ void Router::checkAllMissingEdges(void) void Router::generateContains(VertInf *pt) { contains[pt->id].clear(); + enclosingClusters[pt->id].clear(); + + // Don't count points on the border as being inside. + bool countBorder = false; - ShapeRefList::iterator finish = shapeRefs.end(); - for (ShapeRefList::iterator i = shapeRefs.begin(); i != finish; ++i) + // Compute enclosing shapes. + ShapeRefList::const_iterator finish = shapeRefs.end(); + for (ShapeRefList::const_iterator i = shapeRefs.begin(); i != finish; ++i) { - Polygn poly = copyPoly(*i); - if (inPoly(poly, pt->point)) + if (inPoly((*i)->polygon(), pt->point, countBorder)) { contains[pt->id].insert((*i)->id()); } - freePoly(poly); + } + + // Computer enclosing Clusters + ClusterRefList::const_iterator clFinish = clusterRefs.end(); + for (ClusterRefList::const_iterator i = clusterRefs.begin(); + i != clFinish; ++i) + { + if (inPolyGen((*i)->polygon(), pt->point)) + { + enclosingClusters[pt->id].insert((*i)->id()); + } } } -void Router::adjustContainsWithAdd(const Polygn& poly, const int p_shape) +void Router::adjustClustersWithAdd(const PolygonInterface& poly, + const int p_cluster) { for (VertInf *k = vertices.connsBegin(); k != vertices.shapesBegin(); k = k->lstNext) { - if (inPoly(poly, k->point)) + if (inPolyGen(poly, k->point)) { - contains[k->id].insert(p_shape); + enclosingClusters[k->id].insert(p_cluster); } } } -void Router::adjustContainsWithDel(const int p_shape) +void Router::adjustClustersWithDel(const int p_cluster) +{ + for (ContainsMap::iterator k = enclosingClusters.begin(); + k != enclosingClusters.end(); ++k) + { + (*k).second.erase(p_cluster); + } +} + + +void Router::adjustContainsWithAdd(const Polygon& poly, const int p_shape) { + // Don't count points on the border as being inside. + bool countBorder = false; + for (VertInf *k = vertices.connsBegin(); k != vertices.shapesBegin(); k = k->lstNext) { - contains[k->id].erase(p_shape); + if (inPoly(poly, k->point, countBorder)) + { + contains[k->id].insert(p_shape); + } + } +} + + +void Router::adjustContainsWithDel(const int p_shape) +{ + for (ContainsMap::iterator k = contains.begin(); k != contains.end(); ++k) + { + (*k).second.erase(p_shape); } } @@ -537,14 +1054,21 @@ static double AngleAFromThreeSides(const double a, const double b, void Router::markConnectors(ShapeRef *shape) { - assert(SelectiveReroute); + if (RubberBandRouting) + { + // When rubber-band routing, we do no reroute connectors that + // may have a better route, only invalid connectors. + return; + } + + COLA_ASSERT(SelectiveReroute); - ConnRefList::iterator end = connRefs.end(); - for (ConnRefList::iterator it = connRefs.begin(); it != end; ++it) + ConnRefList::const_iterator end = connRefs.end(); + for (ConnRefList::const_iterator it = connRefs.begin(); it != end; ++it) { ConnRef *conn = (*it); - if (conn->_route.pn == 0) + if (conn->_route.empty()) { // Ignore uninitialised connectors. continue; @@ -556,7 +1080,7 @@ void Router::markConnectors(ShapeRef *shape) } Point start = conn->_route.ps[0]; - Point end = conn->_route.ps[conn->_route.pn - 1]; + Point end = conn->_route.ps[conn->_route.size() - 1]; double conndist = conn->_route_dist; @@ -609,12 +1133,12 @@ void Router::markConnectors(ShapeRef *shape) Point n_p2(p2.x - p1.x, p2.y - p1.y); Point n_start(start.x - p1.x, start.y - p1.y); Point n_end(end.x - p1.x, end.y - p1.y); - //printf("n_p2: (%.1f, %.1f)\n", n_p2.x, n_p2.y); - //printf("n_start: (%.1f, %.1f)\n", n_start.x, n_start.y); - //printf("n_end: (%.1f, %.1f)\n", n_end.x, n_end.y); + //db_printf("n_p2: (%.1f, %.1f)\n", n_p2.x, n_p2.y); + //db_printf("n_start: (%.1f, %.1f)\n", n_start.x, n_start.y); + //db_printf("n_end: (%.1f, %.1f)\n", n_end.x, n_end.y); double theta = 0 - atan2(n_p2.y, n_p2.x); - //printf("theta = %.2f\n", theta * (180 / PI)); + //db_printf("theta = %.2f\n", theta * (180 / PI)); Point r_p1(0, 0); Point r_p2 = n_p2; @@ -630,16 +1154,15 @@ void Router::markConnectors(ShapeRef *shape) start.y = cosv * n_start.y + sinv * n_start.x; end.x = cosv * n_end.x - sinv * n_end.y; end.y = cosv * n_end.y + sinv * n_end.x; - //printf("r_p2: (%.1f, %.1f)\n", r_p2.x, r_p2.y); - //printf("r_start: (%.1f, %.1f)\n", start.x, start.y); - //printf("r_end: (%.1f, %.1f)\n", end.x, end.y); + //db_printf("r_p2: (%.1f, %.1f)\n", r_p2.x, r_p2.y); + //db_printf("r_start: (%.1f, %.1f)\n", start.x, start.y); + //db_printf("r_end: (%.1f, %.1f)\n", end.x, end.y); - if (((int) r_p2.y) != 0) + // This might be slightly off. + if (fabs(r_p2.y) > 0.0001) { - printf("r_p2.y: %f != 0\n", r_p2.y); - std::abort(); + db_printf("r_p2.y: %f != 0\n", r_p2.y); } - // This might be slightly off. r_p2.y = 0; offy = r_p1.y; @@ -679,13 +1202,13 @@ void Router::markConnectors(ShapeRef *shape) x = ((b*c) + (a*d)) / (b + d); } - //printf("%.1f, %.1f, %.1f, %.1f\n", a, b, c, d); - //printf("x = %.1f\n", x); + //db_printf("%.1f, %.1f, %.1f, %.1f\n", a, b, c, d); + //db_printf("x = %.1f\n", x); x = std::max(min, x); x = std::min(max, x); - //printf("x = %.1f\n", x); + //db_printf("x = %.1f\n", x); Point xp; if (p1.x == p2.x) @@ -698,20 +1221,20 @@ void Router::markConnectors(ShapeRef *shape) xp.x = x; xp.y = offy; } - //printf("(%.1f, %.1f)\n", xp.x, xp.y); + //db_printf("(%.1f, %.1f)\n", xp.x, xp.y); - e1 = dist(start, xp); - e2 = dist(xp, end); + e1 = euclideanDist(start, xp); + e2 = euclideanDist(xp, end); estdist = e1 + e2; - //printf("is %.1f < %.1f\n", estdist, conndist); + //db_printf("is %.1f < %.1f\n", estdist, conndist); if (estdist < conndist) { #ifdef SELECTIVE_DEBUG //double angle = AngleAFromThreeSides(dist(start, end), // e1, e2); - printf("[%3d] - Possible better path found (%.1f < %.1f)\n", + db_printf("[%3d] - Possible better path found (%.1f < %.1f)\n", conn->_id, estdist, conndist); #endif conn->_needs_reroute_flag = true; @@ -723,6 +1246,81 @@ void Router::markConnectors(ShapeRef *shape) } +ConnType Router::validConnType(const ConnType select) const +{ + if (select != ConnType_None) + { + if ((select == ConnType_Orthogonal) && _orthogonalRouting) + { + return ConnType_Orthogonal; + } + else if ((select == ConnType_PolyLine) && _polyLineRouting) + { + return ConnType_PolyLine; + } + } + + if (_polyLineRouting) + { + return ConnType_PolyLine; + } + else if (_orthogonalRouting) + { + return ConnType_Orthogonal; + } + return ConnType_None; +} + + +void Router::setRoutingPenalty(const PenaltyType penType, const double penVal) +{ + COLA_ASSERT(penType < lastPenaltyMarker); + if (penVal < 0) + { + // Set some sensible penalty. + switch (penType) + { + case segmentPenalty: + _routingPenalties[penType] = 50; + break; + case fixedSharedPathPenalty: + _routingPenalties[penType] = 110; + break; + case anglePenalty: + _routingPenalties[penType] = 50; + break; + case crossingPenalty: + _routingPenalties[penType] = 200; + break; + case clusterCrossingPenalty: + _routingPenalties[penType] = 4000; + break; + default: + _routingPenalties[penType] = 50; + break; + } + } + else + { + _routingPenalties[penType] = penVal; + } +} + + +double Router::routingPenalty(const PenaltyType penType) const +{ + COLA_ASSERT(penType < lastPenaltyMarker); + return _routingPenalties[penType]; +} + + +double& Router::penaltyRef(const PenaltyType penType) +{ + COLA_ASSERT(penType < lastPenaltyMarker); + return _routingPenalties[penType]; +} + + void Router::printInfo(void) { FILE *fp = stdout; @@ -735,6 +1333,7 @@ void Router::printInfo(void) int st_endpoints = 0; int st_valid_shape_visedges = 0; int st_valid_endpt_visedges = 0; + int st_orthogonal_visedges = 0; int st_invalid_visedges = 0; VertInf *finish = vertices.end(); for (VertInf *t = vertices.connsBegin(); t != finish; t = t->lstNext) @@ -775,9 +1374,15 @@ void Router::printInfo(void) { st_invalid_visedges++; } + for (EdgeInf *t = visOrthogGraph.begin(); t != visOrthogGraph.end(); + t = t->lstNext) + { + st_orthogonal_visedges++; + } fprintf(fp, "Number of shapes: %d\n", st_shapes); fprintf(fp, "Number of vertices: %d (%d real, %d endpoints)\n", st_vertices + st_endpoints, st_vertices, st_endpoints); + fprintf(fp, "Number of orhtog_vis_edges: %d\n", st_orthogonal_visedges); fprintf(fp, "Number of vis_edges: %d (%d valid [%d normal, %d endpt], " "%d invalid)\n", st_valid_shape_visedges + st_invalid_visedges + st_valid_endpt_visedges, st_valid_shape_visedges + @@ -787,12 +1392,462 @@ void Router::printInfo(void) fprintf(fp, "checkVisEdge tally: %d\n", st_checked_edges); fprintf(fp, "----------------------\n"); - fprintf(fp, "ADDS: "); timers.Print(tmAdd); - fprintf(fp, "DELS: "); timers.Print(tmDel); - fprintf(fp, "MOVS: "); timers.Print(tmMov); - fprintf(fp, "***S: "); timers.Print(tmSev); - fprintf(fp, "PTHS: "); timers.Print(tmPth); + fprintf(fp, "ADDS: "); timers.Print(tmAdd, fp); + fprintf(fp, "DELS: "); timers.Print(tmDel, fp); + fprintf(fp, "MOVS: "); timers.Print(tmMov, fp); + fprintf(fp, "***S: "); timers.Print(tmSev, fp); + fprintf(fp, "PTHS: "); timers.Print(tmPth, fp); + fprintf(fp, "OrthogGraph: "); timers.Print(tmOrthogGraph, fp); + fprintf(fp, "OrthogRoute: "); timers.Print(tmOrthogRoute, fp); + fprintf(fp, "OrthogCentre: "); timers.Print(tmOrthogCentre, fp); + fprintf(fp, "OrthogNudge: "); timers.Print(tmOrthogNudge, fp); fprintf(fp, "\n"); + timers.Reset(); +} + + +static const double LIMIT = 100000000; + +static void reduceRange(double& val) +{ + val = std::min(val, LIMIT); + val = std::max(val, -LIMIT); +} + + +//============================================================================= +// The following methods are for testing and debugging. + + +bool Router::existsOrthogonalPathOverlap(void) +{ + ConnRefList::iterator fin = connRefs.end(); + for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) + { + Avoid::Polygon iRoute = (*i)->displayRoute(); + ConnRefList::iterator j = i; + for (++j; j != fin; ++j) + { + // Determine if this pair overlap + Avoid::Polygon jRoute = (*j)->displayRoute(); + CrossingsInfoPair crossingInfo = std::make_pair(0, 0); + for (size_t jInd = 1; jInd < jRoute.size(); ++jInd) + { + const bool finalSegment = ((jInd + 1) == jRoute.size()); + CrossingsInfoPair crossingInfo = countRealCrossings( + iRoute, true, jRoute, jInd, true, + finalSegment, NULL, NULL, *i, *j); + + if ((crossingInfo.second & CROSSING_SHARES_PATH) && + (crossingInfo.second & CROSSING_SHARES_FIXED_SEGMENT) && + !(crossingInfo.second & CROSSING_SHARES_PATH_AT_END)) + { + // We looking for fixedSharedPaths and there is a + // fixedSharedPath. + return true; + } + } + } + } + return false; +} + + +bool Router::existsOrthogonalTouchingCorners(void) +{ + ConnRefList::iterator fin = connRefs.end(); + for (ConnRefList::iterator i = connRefs.begin(); i != fin; ++i) + { + Avoid::Polygon iRoute = (*i)->displayRoute(); + ConnRefList::iterator j = i; + for (++j; j != fin; ++j) + { + // Determine if this pair overlap + Avoid::Polygon jRoute = (*j)->displayRoute(); + CrossingsInfoPair crossingInfo = std::make_pair(0, 0); + for (size_t jInd = 1; jInd < jRoute.size(); ++jInd) + { + const bool finalSegment = ((jInd + 1) == jRoute.size()); + CrossingsInfoPair crossingInfo = countRealCrossings( + iRoute, true, jRoute, jInd, true, + finalSegment, NULL, NULL, *i, *j); + + if (crossingInfo.second & CROSSING_TOUCHES) + { + return true; + } + } + } + } + return false; +} + + +void Router::outputInstanceToSVG(std::string instanceName) +{ + std::string filename; + if (!instanceName.empty()) + { + filename = instanceName; + } + else + { + filename = "libavoid-debug"; + } + filename += ".svg"; + FILE *fp = fopen(filename.c_str(), "w"); + + if (fp == NULL) + { + return; + } + + double minX = LIMIT; + double minY = LIMIT; + double maxX = -LIMIT; + double maxY = -LIMIT; + + VertInf *curr = vertices.connsBegin(); + while (curr) + { + Point p = curr->point; + + reduceRange(p.x); + reduceRange(p.y); + + if (p.x > -LIMIT) + { + minX = std::min(minX, p.x); + } + if (p.x < LIMIT) + { + maxX = std::max(maxX, p.x); + } + if (p.y > -LIMIT) + { + minY = std::min(minY, p.y); + } + if (p.y < LIMIT) + { + maxY = std::max(maxY, p.y); + } + curr = curr->lstNext; + } + minX -= 50; + minY -= 50; + maxX += 50; + maxY += 50; + + fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + fprintf(fp, "<svg xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" xmlns=\"http://www.w3.org/2000/svg\" width=\"100%%\" height=\"100%%\" viewBox=\"%g %g %g %g\">\n", minX, minY, maxX - minX, maxY - minY); + + // Output source code to generate this instance of the router. + fprintf(fp, "<!-- Source code to generate this instance:\n"); + fprintf(fp, "#include \"libavoid/libavoid.h\"\n"); + fprintf(fp, "using namespace Avoid;\n"); + fprintf(fp, "int main(void) {\n"); + fprintf(fp, " Router *router = new Router(\n"); + fprintf(fp, " PolyLineRouting | OrthogonalRouting);\n"); + for (size_t p = 0; p < lastPenaltyMarker; ++p) + { + fprintf(fp, " router->setRoutingPenalty((PenaltyType)%lu, %g);\n", + static_cast<long unsigned int>(p), _routingPenalties[p]); + } + fprintf(fp, " router->setOrthogonalNudgeDistance(%g);\n\n", + orthogonalNudgeDistance()); + ShapeRefList::iterator shRefIt = shapeRefs.begin(); + while (shRefIt != shapeRefs.end()) + { + ShapeRef *shRef = *shRefIt; + fprintf(fp, " Polygon poly%u(%lu);\n", + shRef->id(), static_cast<long unsigned int>(shRef->polygon().size())); + for (size_t i = 0; i < shRef->polygon().size(); ++i) + { + fprintf(fp, " poly%u.ps[%lu] = Point(%g, %g);\n", + shRef->id(), static_cast<long unsigned int>(i), shRef->polygon().at(i).x, + shRef->polygon().at(i).y); + } + fprintf(fp, " ShapeRef *shapeRef%u = new ShapeRef(router, poly%u, " + "%u);\n", shRef->id(), shRef->id(), shRef->id()); + fprintf(fp, " router->addShape(shapeRef%u);\n\n", shRef->id()); + ++shRefIt; + } + ConnRefList::reverse_iterator revConnRefIt = connRefs.rbegin(); + while (revConnRefIt != connRefs.rend()) + { + ConnRef *connRef = *revConnRefIt; + fprintf(fp, " ConnRef *connRef%u = new ConnRef(router, %u);\n", + connRef->id(), connRef->id()); + if (connRef->src()) + { + fprintf(fp, " ConnEnd srcPt%u(Point(%g, %g), %u);\n", + connRef->id(), connRef->src()->point.x, + connRef->src()->point.y, connRef->src()->visDirections); + fprintf(fp, " connRef%u->setSourceEndpoint(srcPt%u);\n", + connRef->id(), connRef->id()); + } + if (connRef->dst()) + { + fprintf(fp, " ConnEnd dstPt%u(Point(%g, %g), %u);\n", + connRef->id(), connRef->dst()->point.x, + connRef->dst()->point.y, connRef->dst()->visDirections); + fprintf(fp, " connRef%u->setDestEndpoint(dstPt%u);\n", + connRef->id(), connRef->id()); + } + fprintf(fp, " connRef%u->setRoutingType((ConnType)%u);\n\n", + connRef->id(), connRef->routingType()); + ++revConnRefIt; + } + fprintf(fp, " router->processTransaction();\n"); + fprintf(fp, " router->outputInstanceToSVG();\n"); + fprintf(fp, " delete router;\n"); + fprintf(fp, " return 0;\n"); + fprintf(fp, "};\n"); + fprintf(fp, "-->\n"); + + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "inkscape:label=\"ShapesPoly\">\n"); + shRefIt = shapeRefs.begin(); + while (shRefIt != shapeRefs.end()) + { + ShapeRef *shRef = *shRefIt; + + fprintf(fp, "<path id=\"poly-%u\" style=\"stroke-width: 1px; " + "stroke: black; fill: blue; fill-opacity: 0.3;\" d=\"", + shRef->id()); + for (size_t i = 0; i < shRef->polygon().size(); ++i) + { + fprintf(fp, "%c %g,%g ", ((i == 0) ? 'M' : 'L'), + shRef->polygon().at(i).x, shRef->polygon().at(i).y); + } + fprintf(fp, "Z\" />\n"); + ++shRefIt; + } + fprintf(fp, "</g>\n"); + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"ShapesRect\">\n"); + shRefIt = shapeRefs.begin(); + while (shRefIt != shapeRefs.end()) + { + ShapeRef *shRef = *shRefIt; + double minX, minY, maxX, maxY; + shRef->polygon().getBoundingRect(&minX, &minY, &maxX, &maxY); + + fprintf(fp, "<rect id=\"rect-%u\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" " + "style=\"stroke-width: 1px; stroke: black; fill: blue; fill-opacity: 0.3;\" />\n", + shRef->id(), minX, minY, maxX - minX, maxY - minY); + ++shRefIt; + } + fprintf(fp, "</g>\n"); + + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "inkscape:label=\"VisGraph\"" + ">\n"); + EdgeInf *finish = NULL; + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"VisGraph-shape\"" + ">\n"); + finish = visGraph.end(); + for (EdgeInf *t = visGraph.begin(); t != finish; t = t->lstNext) + { + std::pair<VertID, VertID> ids = t->ids(); + bool isShape = (ids.first.isShape) && (ids.second.isShape); + if (!isShape) + { + continue; + } + std::pair<Point, Point> ptpair = t->points(); + Point p1 = ptpair.first; + Point p2 = ptpair.second; + + reduceRange(p1.x); + reduceRange(p1.y); + reduceRange(p2.x); + reduceRange(p2.y); + + fprintf(fp, "<path d=\"M %g,%g L %g,%g\" " + "style=\"fill: none; stroke: %s; stroke-width: 1px;\" />\n", + p1.x, p1.y, p2.x, p2.y, + (!(ids.first.isShape) || !(ids.second.isShape)) ? "green" : + "red"); + } + fprintf(fp, "</g>\n"); + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"VisGraph-conn\"" + ">\n"); + finish = visGraph.end(); + for (EdgeInf *t = visGraph.begin(); t != finish; t = t->lstNext) + { + std::pair<VertID, VertID> ids = t->ids(); + bool isShape = (ids.first.isShape) && (ids.second.isShape); + if (isShape) + { + continue; + } + std::pair<Point, Point> ptpair = t->points(); + Point p1 = ptpair.first; + Point p2 = ptpair.second; + + reduceRange(p1.x); + reduceRange(p1.y); + reduceRange(p2.x); + reduceRange(p2.y); + + fprintf(fp, "<path d=\"M %g,%g L %g,%g\" " + "style=\"fill: none; stroke: %s; stroke-width: 1px;\" />\n", + p1.x, p1.y, p2.x, p2.y, + (!(ids.first.isShape) || !(ids.second.isShape)) ? "green" : + "red"); + } + fprintf(fp, "</g>\n"); + fprintf(fp, "</g>\n"); + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"OrthogVisGraph\">\n"); + finish = visOrthogGraph.end(); + for (EdgeInf *t = visOrthogGraph.begin(); t != finish; t = t->lstNext) + { + std::pair<Point, Point> ptpair = t->points(); + Point p1 = ptpair.first; + Point p2 = ptpair.second; + + reduceRange(p1.x); + reduceRange(p1.y); + reduceRange(p2.x); + reduceRange(p2.y); + + std::pair<VertID, VertID> ids = t->ids(); + + fprintf(fp, "<path d=\"M %g,%g L %g,%g\" " + "style=\"fill: none; stroke: %s; stroke-width: 1px;\" />\n", + p1.x, p1.y, p2.x, p2.y, + (!(ids.first.isShape) || !(ids.second.isShape)) ? "green" : + "red"); + } + fprintf(fp, "</g>\n"); + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"RawConnectors\"" + ">\n"); + ConnRefList::iterator connRefIt = connRefs.begin(); + while (connRefIt != connRefs.end()) + { + ConnRef *connRef = *connRefIt; + + PolyLine route = connRef->route(); + if (!route.empty()) + { + fprintf(fp, "<path id=\"raw-%u\" d=\"M %g,%g ", connRef->id(), + route.ps[0].x, route.ps[0].y); + for (size_t i = 1; i < route.size(); ++i) + { + fprintf(fp, "L %g,%g ", route.ps[i].x, route.ps[i].y); + } + fprintf(fp, "\" "); + if (connRef->src() && connRef->dst()) + { + fprintf(fp, "debug=\"src: %d dst: %d\" ", + connRef->src()->visDirections, + connRef->dst()->visDirections); + } + fprintf(fp, "style=\"fill: none; stroke: black; " + "stroke-width: 1px;\" />\n"); + } + + ++connRefIt; + } + fprintf(fp, "</g>\n"); + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "style=\"display: none;\" " + "inkscape:label=\"CurvedDisplayConnectors\"" + ">\n"); + connRefIt = connRefs.begin(); + while (connRefIt != connRefs.end()) + { + ConnRef *connRef = *connRefIt; + + PolyLine route = connRef->displayRoute().curvedPolyline(8); + if (!route.empty()) + { + fprintf(fp, "<path id=\"curved-%u\" d=\"M %g,%g ", connRef->id(), + route.ps[0].x, route.ps[0].y); + for (size_t i = 1; i < route.size(); ++i) + { + if (route.ts[i] == 'C') + { + fprintf(fp, "%c %g,%g %g,%g %g,%g", route.ts[i], + route.ps[i].x, route.ps[i].y, + route.ps[i+1].x, route.ps[i+1].y, + route.ps[i+2].x, route.ps[i+2].y); + i += 2; + } + else + { + fprintf(fp, "%c %g,%g ", route.ts[i], + route.ps[i].x, route.ps[i].y); + } + } + fprintf(fp, "\" "); + if (connRef->src() && connRef->dst()) + { + fprintf(fp, "debug=\"src: %d dst: %d\" ", + connRef->src()->visDirections, + connRef->dst()->visDirections); + } + fprintf(fp, "style=\"fill: none; stroke: black; " + "stroke-width: 1px;\" />\n"); + } + + ++connRefIt; + } + fprintf(fp, "</g>\n"); + + + fprintf(fp, "<g inkscape:groupmode=\"layer\" " + "inkscape:label=\"DisplayConnectors\"" + ">\n"); + connRefIt = connRefs.begin(); + while (connRefIt != connRefs.end()) + { + ConnRef *connRef = *connRefIt; + + PolyLine route = connRef->displayRoute(); + if (!route.empty()) + { + fprintf(fp, "<path id=\"disp-%u\" d=\"M %g,%g ", connRef->id(), + route.ps[0].x, route.ps[0].y); + for (size_t i = 1; i < route.size(); ++i) + { + fprintf(fp, "L %g,%g ", route.ps[i].x, route.ps[i].y); + } + fprintf(fp, "\" "); + if (connRef->src() && connRef->dst()) + { + fprintf(fp, "debug=\"src: %d dst: %d\" ", + connRef->src()->visDirections, + connRef->dst()->visDirections); + } + fprintf(fp, "style=\"fill: none; stroke: black; " + "stroke-width: 1px;\" />\n"); + } + + ++connRefIt; + } + fprintf(fp, "</g>\n"); + + + fprintf(fp, "</svg>\n"); + fclose(fp); } diff --git a/src/libavoid/router.h b/src/libavoid/router.h index 597f48c5e..03060b025 100644 --- a/src/libavoid/router.h +++ b/src/libavoid/router.h @@ -2,94 +2,325 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ +//! @file router.h +//! @brief Contains the interface for the Router class. + #ifndef AVOID_ROUTER_H #define AVOID_ROUTER_H -//#define LINEDEBUG +#include <list> +#include <utility> +#include <string> #include "libavoid/shape.h" +#include "libavoid/viscluster.h" #include "libavoid/graph.h" #include "libavoid/timer.h" -#include <list> -#include <utility> -#ifdef LINEDEBUG +#include "libavoid/connector.h" + +#if defined(LINEDEBUG) || defined(ASTAR_DEBUG) || defined(LIBAVOID_SDL) #include <SDL.h> + #ifndef LIBAVOID_SDL + #define LIBAVOID_SDL + #endif #endif namespace Avoid { -class ConnRef; -typedef std::list<ConnRef *> ConnRefList; typedef std::list<unsigned int> IntList; -class MoveInfo; -typedef std::list<MoveInfo *> MoveInfoList; + +class ActionInfo; +typedef std::list<ActionInfo> ActionInfoList; +class ShapeRef; + +//! @brief Flags that can be passed to the router during initialisation +//! to specify options. +enum RouterFlag +{ + //! @brief This option specifies that the router should maintain the + //! structures necessary to allow poly-line connector routing. + PolyLineRouting = 1, + //! @brief This option specifies that the router should maintain the + //! structures necessary to allow orthogonal connector routing. + OrthogonalRouting = 2 +}; static const unsigned int runningTo = 1; static const unsigned int runningFrom = 2; static const unsigned int runningToAndFrom = runningTo | runningFrom; +//! @brief Types of penalty cases that can be used to improve the quality +//! of the connector routes produced. +enum PenaltyType +{ + //! @brief This penalty is applied for each segment in the connector + //! path beyond the first. This should always normally be set + //! when doing orthogonal routing to prevent step-like connector + //! paths. + segmentPenalty = 0, + //! @brief This penalty is applied in its full amount to tight acute + //! bends in the connector path. A smaller portion of the penalty + //! is applied for slight bends, i.e., where the bend is close to + //! 180 degrees. This is useful for polyline routing where there + //! is some evidence that tighter corners are worse for + //! readability, but that slight bends might not be so bad, + //! especially when smoothed by curves. + anglePenalty, + //! @brief This penalty is applied whenever a connector path crosses + //! another connector path. It takes shared paths into + //! consideration and the penalty is only applied if there + //! is an actual crossing. + //! @note This penalty is still experimental! It is not recommended + //! for normal use. + crossingPenalty, + //! @brief This penalty is applied whenever a connector path crosses + //! a cluster boundary. + //! @note This penalty is still experimental! It is not recommended + //! for normal use. + clusterCrossingPenalty, + //! @brief This penalty is applied whenever a connector path shares + //! some segments with an immovable portion of an existing + //! connector route (such as the first or last segment of a + //! connector). + //! @note This penalty is still experimental! It is not recommended + //! for normal use. + fixedSharedPathPenalty, + // Used for determining the size of the penalty array. + // This should always we the last value in the enum. + lastPenaltyMarker +}; + + +static const double noPenalty = 0; +static const double chooseSensiblePenalty = -1; + +//! @brief The Router class represents a libavoid router instance. +//! +//! Usually you would keep a separate Router instance for each diagram +//! or layout you have open in your application. +// class Router { public: - Router(); + //! @brief Constructor for router instance. + //! + //! @param[in] flags One or more Avoid::RouterFlag options to + //! control the behaviour of the router. + Router(const unsigned int flags); + + //! @brief Destructor for router instance. + //! + //! @note Destroying a router instance will delete all remaining + //! shapes and connectors, thereby invalidating any existing + //! pointers to them. + ~Router(); ShapeRefList shapeRefs; ConnRefList connRefs; + ClusterRefList clusterRefs; EdgeList visGraph; EdgeList invisGraph; + EdgeList visOrthogGraph; ContainsMap contains; VertInfList vertices; + ContainsMap enclosingClusters; bool PartialTime; bool SimpleRouting; - double segmt_penalty; - double angle_penalty; - double crossing_penalty; - + bool ClusteredRouting; - bool UseAStarSearch; + // Poly-line routing options: bool IgnoreRegions; - bool SelectiveReroute; - bool IncludeEndpoints; bool UseLeesAlgorithm; bool InvisibilityGrph; - bool ConsolidateMoves; + + // General routing options: + bool SelectiveReroute; + bool PartialFeedback; + bool RubberBandRouting; + // Instrumentation: Timer timers; int st_checked_edges; -#ifdef LINEDEBUG +#ifdef LIBAVOID_SDL SDL_Surface *avoid_screen; #endif + //! @brief Allows setting of the behaviour of the router in regard + //! to transactions. This controls whether transactions are + //! used to queue changes and process them effeciently at once + //! or they are instead processed immediately. + //! + //! It is more efficient to perform actions like shape movement, + //! addition or deletion as batch tasks, and reroute the necessary + //! connectors just once after these actions have been performed. + //! For this reason, libavoid allows you to group such actions into + //! "transactions" that are processed efficiently when the + //! processTransaction() method is called. + //! + //! By default, the router will process all actions as tranactions. + //! If transactionUse() is set to false, then all actions will get + //! processed immediately, and cause immediate routing callbacks to + //! all affected connectors after each action. + //! + //! @param[in] transactions A boolean value specifying whether to + //! use transactions. + //! + void setTransactionUse(const bool transactions); + + //! @brief Reports whether the router groups actions into transactions. + //! + //! @return A boolean value describing whether transactions are in use. + //! + //! @sa setTransactionUse + //! @sa processTransaction + //! + bool transactionUse(void) const; + + //! @brief Finishes the current transaction and processes all the + //! queued object changes efficiently. + //! + //! This method will efficiently process all moves, additions and + //! deletions that have occurred since processTransaction() was + //! last called. + //! + //! If transactionUse() is false, then all actions will have been + //! processed immediately and this method will do nothing. + //! + //! @return A boolean value describing whether there were any actions + //! to process. + //! + //! @sa setTransactionUse + //! + bool processTransaction(void); + + //! @brief Add a shape to the router scene. + //! + //! This shape will be considered to be an obstacle. Calling this + //! method will cause connectors intersecting the added shape to + //! be marked as needing to be rerouted. + //! + //! @param[in] shape Pointer reference to the shape being added. + //! void addShape(ShapeRef *shape); - void delShape(ShapeRef *shape); - void moveShape(ShapeRef *shape, Polygn *newPoly, + + //! @brief Remove a shape from the router scene. + //! + //! Connectors that could have a better (usually shorter) path after + //! the removal of this shape will be marked as needing to be rerouted. + //! + //! @param[in] shape Pointer reference to the shape being removed. + //! + void removeShape(ShapeRef *shape); + + //! @brief Move or resize an existing shape within the router scene. + //! + //! A new polygon for the shape can be given to effectively move or + //! resize the shape with the scene. Connectors that intersect the + //! new shape polygon, or that could have a better (usually shorter) + //! path after the change, will be marked as needing to be rerouted. + //! + //! @param[in] shape Pointer reference to the shape being + //! moved/resized. + //! @param[in] newPoly The new polygon boundary for the shape. + //! @param[in] first_move This option is used for some advanced + //! (currently undocumented) behaviour and + //! it should be ignored for the moment. + //! + void moveShape(ShapeRef *shape, const Polygon& newPoly, const bool first_move = false); - void processMoves(void); - + + //! @brief Move an existing shape within the router scene by a relative + //! distance. + //! + //! Connectors that intersect the shape's new position, or that could + //! have a better (usually shorter) path after the change, will be + //! marked as needing to be rerouted. + //! + //! @param[in] shape Pointer reference to the shape being moved. + //! @param[in] xDiff The distance to move the shape in the + //! x dimension. + //! @param[in] yDiff The distance to move the shape in the + //! y dimension. + //! + void moveShape(ShapeRef *shape, const double xDiff, const double yDiff); + + //! @brief Sets a spacing distance for overlapping orthogonal + //! connectors to be nudged apart. + //! + //! By default, this distance is set to a value of 4. + //! + //! This method does not re-trigger post-processing of connectors. + //! The new distance will be used the next time rerouting is performed. + //! + //! @param[in] dist The distance to be used for orthogonal nudging. + //! + void setOrthogonalNudgeDistance(const double dist); + + //! @brief Returns the spacing distance that overlapping orthogonal + //! connecotrs are nudged apart. + //! + //! @return The current spacing distance used for orthogonal nudging. + //! + double orthogonalNudgeDistance(void) const; + + //! @brief Sets or removes penalty values that are applied during + //! connector routing. + //! + //! By default, libavoid will produce shortest path routes between + //! the source and destination points for each connector. There are + //! several penalties that can be applied during this stage to + //! improve the aesthetics of the routes generated. These different + //! penalties are specified and explained by the PenaltyType enum. + //! + //! If a value of zero or Avoid::noPenalty is given then the penalty + //! for this case will be removed. If no penalty argument (or a + //! negative value) is specified when calling this method, then a + //! sensible penalty value will be automatically chosen. + //! + //! @param[in] penType The type of penalty, a PenaltyType. + //! @param[in] penVal The value to be applied for each occurance + //! of the penalty case. + //! + void setRoutingPenalty(const PenaltyType penType, + const double penVal = chooseSensiblePenalty); + + //! @brief Returns the current penalty value for a particular + //! routing penalty case. + //! + //! @param[in] penType The type of penalty, a PenaltyType. + //! @return The penalty value for the specified penalty case. + //! + double routingPenalty(const PenaltyType penType) const; + + void addCluster(ClusterRef *cluster); + void delCluster(ClusterRef *cluster); + void attachedConns(IntList &conns, const unsigned int shapeId, const unsigned int type); void attachedShapes(IntList &shapes, const unsigned int shapeId, @@ -98,15 +329,49 @@ class Router { void markConnectors(ShapeRef *shape); void generateContains(VertInf *pt); void printInfo(void); + void outputInstanceToSVG(std::string filename = std::string()); + unsigned int assignId(const unsigned int suggestedId); + void regenerateStaticBuiltGraph(void); + void destroyOrthogonalVisGraph(void); + void setStaticGraphInvalidated(const bool invalidated); + ConnType validConnType(const ConnType select = ConnType_None) const; + bool shapeInQueuedActionList(ShapeRef *shape) const; + double& penaltyRef(const PenaltyType penType); + bool existsOrthogonalPathOverlap(void); + bool existsOrthogonalTouchingCorners(void); + private: - void newBlockingShape(Polygn *poly, int pid); + friend class ConnRef; + + void modifyConnector(ConnRef *conn); + void modifyConnector(ConnRef *conn, unsigned int type, + const ConnEnd &connEnd); + void removeQueuedConnectorActions(ConnRef *conn); + void newBlockingShape(const Polygon& poly, int pid); void checkAllBlockedEdges(int pid); void checkAllMissingEdges(void); - void adjustContainsWithAdd(const Polygn& poly, const int p_shape); + void adjustContainsWithAdd(const Polygon& poly, const int p_shape); void adjustContainsWithDel(const int p_shape); - void callbackAllInvalidConnectors(void); + void adjustClustersWithAdd(const PolygonInterface& poly, + const int p_cluster); + void adjustClustersWithDel(const int p_cluster); + void rerouteAndCallbackConnectors(void); + bool idIsUnique(const unsigned int id) const; + void improveCrossings(void); + + ActionInfoList actionList; + unsigned int _largestAssignedId; + bool _consolidateActions; + double _orthogonalNudgeDistance; + double _routingPenalties[lastPenaltyMarker]; + + public: + // Overall modes: + bool _polyLineRouting; + bool _orthogonalRouting; - MoveInfoList moveList; + bool _staticGraphInvalidated; + bool _inCrossingPenaltyReroutingStage; }; } diff --git a/src/libavoid/shape.cpp b/src/libavoid/shape.cpp index c0ff2f6e8..ac3f0c193 100644 --- a/src/libavoid/shape.cpp +++ b/src/libavoid/shape.cpp @@ -2,53 +2,57 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ -#include <cassert> #include "libavoid/shape.h" #include "libavoid/graph.h" // For alertConns #include "libavoid/vertices.h" -#include "libavoid/polyutil.h" #include "libavoid/router.h" +#include "libavoid/debug.h" +#include "libavoid/assertions.h" namespace Avoid { -ShapeRef::ShapeRef(Router *router, unsigned int id, Polygn& ply) +ShapeRef::ShapeRef(Router *router, Polygon& ply, const unsigned int id) : _router(router) - , _id(id) - , _poly(copyPoly(ply)) + , _poly(ply) , _active(false) , _inMoveList(false) , _firstVert(NULL) , _lastVert(NULL) { + _id = router->assignId(id); + bool isShape = true; - VertID i = VertID(id, isShape, 0); + VertID i = VertID(_id, isShape, 0); + const bool addToRouterNow = false; VertInf *last = NULL; VertInf *node = NULL; - for (int pt_i = 0; pt_i < _poly.pn; pt_i++) + for (size_t pt_i = 0; pt_i < _poly.size(); ++pt_i) { - node = new VertInf(_router, i, _poly.ps[pt_i]); + node = new VertInf(_router, i, _poly.ps[pt_i], addToRouterNow); if (!_firstVert) { @@ -69,17 +73,22 @@ ShapeRef::ShapeRef(Router *router, unsigned int id, Polygn& ply) _lastVert->shNext = _firstVert; _firstVert->shPrev = _lastVert; - - makeActive(); } ShapeRef::~ShapeRef() { - assert(_firstVert != NULL); - - makeInactive(); + COLA_ASSERT(!_router->shapeInQueuedActionList(this)); + if (_active) + { + // Destroying a shape without calling removeShape(), so do it now. + _router->removeShape(this); + _router->processTransaction(); + } + + COLA_ASSERT(_firstVert != NULL); + VertInf *it = _firstVert; do { @@ -90,41 +99,37 @@ ShapeRef::~ShapeRef() } while (it != _firstVert); _firstVert = _lastVert = NULL; - - freePoly(_poly); } -void ShapeRef::setNewPoly(Polygn& poly) +void ShapeRef::setNewPoly(const Polygon& poly) { - assert(_firstVert != NULL); - assert(_poly.pn == poly.pn); + COLA_ASSERT(_firstVert != NULL); + COLA_ASSERT(_poly.size() == poly.size()); VertInf *curr = _firstVert; - for (int pt_i = 0; pt_i < _poly.pn; pt_i++) + for (size_t pt_i = 0; pt_i < _poly.size(); ++pt_i) { - assert(curr->visListSize == 0); - assert(curr->invisListSize == 0); + COLA_ASSERT(curr->visListSize == 0); + COLA_ASSERT(curr->invisListSize == 0); // Reset with the new polygon point. curr->Reset(poly.ps[pt_i]); curr->pathNext = NULL; - curr->pathDist = 0; curr = curr->shNext; } - assert(curr == _firstVert); + COLA_ASSERT(curr == _firstVert); - freePoly(_poly); - _poly = copyPoly(poly); + _poly = poly; } void ShapeRef::makeActive(void) { - assert(!_active); + COLA_ASSERT(!_active); - // Add to connRefs list. + // Add to shapeRefs list. _pos = _router->shapeRefs.insert(_router->shapeRefs.begin(), this); // Add points to vertex list. @@ -144,9 +149,9 @@ void ShapeRef::makeActive(void) void ShapeRef::makeInactive(void) { - assert(_active); + COLA_ASSERT(_active); - // Remove from connRefs list. + // Remove from shapeRefs list. _router->shapeRefs.erase(_pos); // Remove points from vertex list. @@ -162,7 +167,13 @@ void ShapeRef::makeInactive(void) _active = false; } - + + +bool ShapeRef::isActive(void) const +{ + return _active; +} + VertInf *ShapeRef::firstVert(void) { @@ -176,19 +187,19 @@ VertInf *ShapeRef::lastVert(void) } -unsigned int ShapeRef::id(void) +unsigned int ShapeRef::id(void) const { return _id; } -Polygn ShapeRef::poly(void) +const Polygon& ShapeRef::polygon(void) const { return _poly; } -Router *ShapeRef::router(void) +Router *ShapeRef::router(void) const { return _router; } @@ -196,13 +207,13 @@ Router *ShapeRef::router(void) void ShapeRef::boundingBox(BBox& bbox) { - assert(_poly.pn > 0); + COLA_ASSERT(!_poly.empty()); bbox.a = bbox.b = _poly.ps[0]; Point& a = bbox.a; Point& b = bbox.b; - for (int i = 1; i < _poly.pn; ++i) + for (size_t i = 1; i < _poly.size(); ++i) { const Point& p = _poly.ps[i]; @@ -223,8 +234,8 @@ void ShapeRef::removeFromGraph(void) // For each vertex. EdgeInfList& visList = tmp->visList; - EdgeInfList::iterator finish = visList.end(); - EdgeInfList::iterator edge; + EdgeInfList::const_iterator finish = visList.end(); + EdgeInfList::const_iterator edge; while ((edge = visList.begin()) != finish) { // Remove each visibility edge @@ -239,6 +250,15 @@ void ShapeRef::removeFromGraph(void) // Remove each invisibility edge delete (*edge); } + + EdgeInfList& orthogList = tmp->orthogVisList; + finish = orthogList.end(); + while ((edge = orthogList.begin()) != finish) + { + // Remove each orthogonal visibility edge + (*edge)->alertConns(); + delete (*edge); + } } } @@ -251,8 +271,8 @@ void ShapeRef::markForMove(void) } else { - fprintf(stderr, "WARNING: two moves queued for same shape prior to " - "rerouting.\n This is not safe.\n"); + db_printf("WARNING: two moves queued for same shape prior to rerouting." + "\n This is not safe.\n"); } } diff --git a/src/libavoid/shape.h b/src/libavoid/shape.h index b654c6eea..df4c98df1 100644 --- a/src/libavoid/shape.h +++ b/src/libavoid/shape.h @@ -2,24 +2,30 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ +//! @file shape.h +//! @brief Contains the interface for the ShapeRef class. + + #ifndef AVOID_SHAPE_H #define AVOID_SHAPE_H @@ -35,21 +41,64 @@ class ShapeRef; typedef std::list<ShapeRef *> ShapeRefList; +//! @brief The ShapeRef class represents a shape object. +//! +//! Shapes are obstacles that connectors must be routed around. They can be +//! placed into a Router scene and can be repositioned or resized (via +//! Router::moveShape()). +//! +//! Usually, it is expected that you would create a ShapeRef for each shape +//! in your diagram and keep that reference in your own shape class. +//! class ShapeRef { public: - ShapeRef(Router *router, unsigned int id, Polygn& poly); - virtual ~ShapeRef(); - void setNewPoly(Polygn& poly); + //! @brief Shape reference constructor. + //! + //! Creates a shape obect reference, but does not yet place it into the + //! Router scene. + //! + //! The poly argument will usually be the boundary of the shape in your + //! application with additional buffer of several pixels on each side. + //! Specifying such a buffer results in connectors leaving a small + //! amount of space around shapes, rather than touching them on the + //! corners or edges. + //! + //! If an ID is not specified, then one will be assigned to the shape. + //! If assigning an ID yourself, note that it should be a unique + //! positive integer. Also, IDs are given to all objects in a scene, + //! so the same ID cannot be given to a shape and a connector for + //! example. + //! + //! @param[in] router The router scene to place the shape into. + //! @param[in] poly A Polygon representing the boundary of the + //! shape. + //! @param[in] id A unique positive integer ID for the shape. + ShapeRef(Router *router, Polygon& poly, const unsigned int id = 0); + //! @brief Shape reference destructor. + //! + //! This will call Router::removeShape() for this shape, if this has + //! not already be called. + ~ShapeRef(); + + //! @brief Returns the ID of this shape. + //! @returns The ID of the shape. + unsigned int id(void) const; + //! @brief Returns a reference to the polygon boundary of this shape. + //! @returns A reference to the polygon boundary of the shape. + const Polygon& polygon(void) const; + //! @brief Returns a pointer to the router scene this shape is in. + //! @returns A pointer to the router scene for this shape. + Router *router(void) const; + + void setNewPoly(const Polygon& poly); VertInf *firstVert(void); VertInf *lastVert(void); - unsigned int id(void); - Polygn poly(void); - Router *router(void); void boundingBox(BBox& bbox); void makeActive(void); void makeInactive(void); + bool isActive(void) const; void removeFromGraph(void); void markForMove(void); @@ -60,7 +109,7 @@ class ShapeRef private: Router *_router; unsigned int _id; - Polygn _poly; + Polygon _poly; bool _active; bool _inMoveList; ShapeRefList::iterator _pos; diff --git a/src/libavoid/static.cpp b/src/libavoid/static.cpp deleted file mode 100644 index 740a4f9c0..000000000 --- a/src/libavoid/static.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - -#include <cassert> -#include <iostream> -#include "libavoid/vertices.h" -#include "libavoid/connector.h" -#include "libavoid/graph.h" -#include "libavoid/static.h" -#include "libavoid/shape.h" -#include "libavoid/visibility.h" -#include "libavoid/debug.h" -#include "libavoid/router.h" - -namespace Avoid { - -static void computeCompleteVis(Router *router); - - -// This should only be used for the static algorithm. -// -// XXX: If to set up the vis graph for incremental it would need -// the shapeRef pointers in obs. -// -void CreateVisGraph(Router *router, Polygn **obs, int n_obs) -{ - for (int poly_i = 0; poly_i < n_obs; poly_i++) - { - unsigned int id = obs[poly_i]->id; - - new ShapeRef(router, id, *(obs[poly_i])); - } - computeCompleteVis(router); -} - - -static void computeCompleteVis(Router *router) -{ - VertInf *beginVert = router->vertices.shapesBegin(); - VertInf *endVert = router->vertices.end(); - for (VertInf *i = beginVert; i != endVert; i = i->lstNext) - { - db_printf("-- CONSIDERING --\n"); - i->id.db_print(); - - for (VertInf *j = i->lstPrev ; j != NULL; j = j->lstPrev) - { - bool knownNew = true; - EdgeInf::checkEdgeVisibility(i, j, knownNew); - } - } -} - - -void DestroyVisGraph(Router *router) -{ - ShapeRefList::iterator sFinish = router->shapeRefs.end(); - ShapeRefList::iterator sCurr; - - while ((sCurr = router->shapeRefs.begin()) != sFinish) - { - ShapeRef *shape = (*sCurr); - - shape->removeFromGraph(); - delete shape; - } - - ConnRefList::iterator cFinish = router->connRefs.end(); - ConnRefList::iterator cCurr; - - while ((cCurr = router->connRefs.begin())!= cFinish) - { - ConnRef *conn = (*cCurr); - - conn->removeFromGraph(); - conn->unInitialise(); - } - // Clear contains info. - router->contains.clear(); - - assert(router->vertices.connsBegin() == NULL); -} - - -} - diff --git a/src/libavoid/static.h b/src/libavoid/static.h deleted file mode 100644 index 18e9ac278..000000000 --- a/src/libavoid/static.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * vim: ts=4 sw=4 et tw=0 wm=0 - * - * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -*/ - - -#ifndef AVOID_STATIC_H -#define AVOID_STATIC_H - -#include "libavoid/geomtypes.h" - - -namespace Avoid { - -class Router; - - -extern void CreateVisGraph(Router *router, Polygn **obstacles, - int n_obstacles); -extern void DestroyVisGraph(Router *router); - -} - - -#endif diff --git a/src/libavoid/timer.cpp b/src/libavoid/timer.cpp index e4349bea9..f8600acbe 100644 --- a/src/libavoid/timer.cpp +++ b/src/libavoid/timer.cpp @@ -2,31 +2,34 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #include <cstdio> #include <cstdlib> -#include <cassert> -using std::abort; #include <climits> #include "libavoid/timer.h" +#include "libavoid/debug.h" +#include "libavoid/assertions.h" namespace Avoid { @@ -52,9 +55,9 @@ void Timer::Reset(void) } -void Timer::Register(const int t, const bool start) +void Timer::Register(const TimerIndex t, const bool start) { - assert(t != tmNon); + COLA_ASSERT(t != tmNon); if (type == tmNon) { @@ -73,11 +76,7 @@ void Timer::Register(const int t, const bool start) void Timer::Start(void) { - if (running) - { - fprintf(stderr, "ERROR: Timer already running in Timer::Start()\n"); - abort(); - } + COLA_ASSERT(!running); cStart[type] = clock(); // CPU time running = true; } @@ -85,11 +84,7 @@ void Timer::Start(void) void Timer::Stop(void) { - if (!running) - { - fprintf(stderr, "ERROR: Timer not running in Timer::Stop()\n"); - abort(); - } + COLA_ASSERT(running); clock_t cStop = clock(); // CPU time running = false; @@ -106,11 +101,7 @@ void Timer::Stop(void) cDiff = cStop - cStart[type]; } - if (cDiff > LONG_MAX) - { - fprintf(stderr, "Error: cDiff overflow in Timer:Stop()\n"); - abort(); - } + COLA_ASSERT(cDiff < LONG_MAX); if (type == tmPth) { @@ -136,11 +127,11 @@ void Timer::Stop(void) } -void Timer::PrintAll(void) +void Timer::PrintAll(FILE *fp) { - for (int i = 0; i < tmCount; i++) + for (unsigned int i = 0; i < tmCount; i++) { - Print(i); + Print((TimerIndex) i, fp); } } @@ -148,14 +139,14 @@ void Timer::PrintAll(void) #define toMsec(tot) ((bigclock_t) ((tot) / (((double) CLOCKS_PER_SEC) / 1000))) #define toAvg(tot, cnt) ((((cnt) > 0) ? ((long double) (tot)) / (cnt) : 0)) -void Timer::Print(const int t) +void Timer::Print(const TimerIndex t, FILE *fp) { bigclock_t avg = toMsec(toAvg(cTotal[t], cTally[t])); bigclock_t pind = toMsec(toAvg(cPath[t], cPathTally[t])); bigclock_t pavg = toMsec(toAvg(cPath[t], cTally[t])); double max = toMsec(cMax[t]); double pmax = toMsec(cPathMax[t]); - printf("\t%lld %d %lld %.0f %lld %d %lld %.0f %lld\n", + fprintf(fp, "\t%lld %d %lld %.0f %lld %d %lld %.0f %lld\n", cTotal[t], cTally[t], avg, max, cPath[t], cPathTally[t], pavg, pmax, pind); } diff --git a/src/libavoid/timer.h b/src/libavoid/timer.h index a7e6081fa..9cab6d7ff 100644 --- a/src/libavoid/timer.h +++ b/src/libavoid/timer.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef PROFILE_H #define PROFILE_H @@ -46,14 +49,20 @@ namespace Avoid { typedef unsigned long long int bigclock_t; -static const int tmCount = 5; - -static const int tmNon = -1; -static const int tmAdd = 0; -static const int tmDel = 1; -static const int tmMov = 2; -static const int tmPth = 3; -static const int tmSev = 4; +enum TimerIndex +{ + tmNon = 0, + tmAdd, + tmDel, + tmMov, + tmPth, + tmSev, + tmOrthogGraph, + tmOrthogRoute, + tmOrthogCentre, + tmOrthogNudge, + tmCount +}; static const bool timerStart = true; @@ -64,12 +73,12 @@ class Timer { public: Timer(); - void Register(const int t, const bool start = timerDelay); + void Register(const TimerIndex t, const bool start = timerDelay); void Start(void); void Stop(void); void Reset(void); - void Print(const int t); - void PrintAll(void); + void Print(TimerIndex, FILE *fp); + void PrintAll(FILE *fp); private: clock_t cStart[tmCount]; @@ -82,7 +91,7 @@ class Timer bool running; long count; - int type, lasttype; + TimerIndex type, lasttype; }; diff --git a/src/libavoid/vertices.cpp b/src/libavoid/vertices.cpp index c2be955ac..85226498a 100644 --- a/src/libavoid/vertices.cpp +++ b/src/libavoid/vertices.cpp @@ -2,33 +2,36 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + +#include <iostream> +#include <cstdlib> + #include "libavoid/vertices.h" #include "libavoid/geometry.h" #include "libavoid/graph.h" // For alertConns #include "libavoid/debug.h" #include "libavoid/router.h" - -#include <iostream> -#include <cstdlib> -#include <cassert> +#include "libavoid/assertions.h" using std::ostream; @@ -76,7 +79,8 @@ bool VertID::operator==(const VertID& rhs) const { return false; } - assert(isShape == rhs.isShape); + // XXX RubberBand search breaks this: + // COLA_ASSERT(isShape == rhs.isShape); return true; } @@ -87,7 +91,7 @@ bool VertID::operator!=(const VertID& rhs) const { return true; } - assert(isShape == rhs.isShape); + COLA_ASSERT(isShape == rhs.isShape); return false; } @@ -133,8 +137,8 @@ void VertID::db_print(void) const } -const int VertID::src = 1; -const int VertID::tar = 2; +const unsigned short VertID::src = 1; +const unsigned short VertID::tar = 2; ostream& operator<<(ostream& os, const VertID& vID) @@ -144,25 +148,57 @@ ostream& operator<<(ostream& os, const VertID& vID) -VertInf::VertInf(Router *router, const VertID& vid, const Point& vpoint) - : _router(router) - , id(vid) - , point(vpoint) - , lstPrev(NULL) - , lstNext(NULL) - , shPrev(NULL) - , shNext(NULL) - , visListSize(0) - , invisListSize(0) - , pathNext(NULL) - , pathDist(0) +VertInf::VertInf(Router *router, const VertID& vid, const Point& vpoint, + const bool addToRouter) + : _router(router), + id(vid), + point(vpoint), + lstPrev(NULL), + lstNext(NULL), + shPrev(NULL), + shNext(NULL), + visListSize(0), + orthogVisListSize(0), + invisListSize(0), + pathNext(NULL), + visDirections(ConnDirNone) +{ + point.id = vid.objID; + point.vn = vid.vn; + + if (addToRouter) + { + _router->vertices.addVertex(this); + } +} + + +VertInf::~VertInf() +{ +} + + +void VertInf::Reset(const VertID& vid, const Point& vpoint) { + id = vid; + point = vpoint; + point.id = id.objID; + point.vn = id.vn; } void VertInf::Reset(const Point& vpoint) { point = vpoint; + point.id = id.objID; + point.vn = id.vn; +} + + +// Returns true if this vertex is not involved in any (in)visibility graphs. +bool VertInf::orphaned(void) +{ + return (visList.empty() && invisList.empty() && orthogVisList.empty()); } @@ -170,15 +206,12 @@ void VertInf::removeFromGraph(const bool isConnVert) { if (isConnVert) { - assert(!(id.isShape)); + COLA_ASSERT(!(id.isShape)); } - VertInf *tmp = this; - // For each vertex. - EdgeInfList& visList = tmp->visList; - EdgeInfList::iterator finish = visList.end(); - EdgeInfList::iterator edge; + EdgeInfList::const_iterator finish = visList.end(); + EdgeInfList::const_iterator edge; while ((edge = visList.begin()) != finish) { // Remove each visibility edge @@ -186,7 +219,14 @@ void VertInf::removeFromGraph(const bool isConnVert) delete (*edge); } - EdgeInfList& invisList = tmp->invisList; + finish = orthogVisList.end(); + while ((edge = orthogVisList.begin()) != finish) + { + // Remove each orthogonal visibility edge. + (*edge)->alertConns(); + delete (*edge); + } + finish = invisList.end(); while ((edge = invisList.begin()) != finish) { @@ -208,7 +248,7 @@ bool directVis(VertInf *src, VertInf *dst) // We better be part of the same instance of libavoid. Router *router = src->_router; - assert(router == dst->_router); + COLA_ASSERT(router == dst->_router); ContainsMap& contains = router->contains; if (!(pID.isShape)) @@ -239,40 +279,40 @@ bool directVis(VertInf *src, VertInf *dst) VertInfList::VertInfList() - : _firstShapeVert(NULL) - , _firstConnVert(NULL) - , _lastShapeVert(NULL) - , _lastConnVert(NULL) - , _shapeVertices(0) - , _connVertices(0) + : _firstShapeVert(NULL), + _firstConnVert(NULL), + _lastShapeVert(NULL), + _lastConnVert(NULL), + _shapeVertices(0), + _connVertices(0) { } #define checkVertInfListConditions() \ do { \ - assert((!_firstConnVert && (_connVertices == 0)) || \ + COLA_ASSERT((!_firstConnVert && (_connVertices == 0)) || \ ((_firstConnVert->lstPrev == NULL) && (_connVertices > 0))); \ - assert((!_firstShapeVert && (_shapeVertices == 0)) || \ + COLA_ASSERT((!_firstShapeVert && (_shapeVertices == 0)) || \ ((_firstShapeVert->lstPrev == NULL) && (_shapeVertices > 0))); \ - assert(!_lastShapeVert || (_lastShapeVert->lstNext == NULL)); \ - assert(!_lastConnVert || (_lastConnVert->lstNext == _firstShapeVert)); \ - assert((!_firstConnVert && !_lastConnVert) || \ + COLA_ASSERT(!_lastShapeVert || (_lastShapeVert->lstNext == NULL)); \ + COLA_ASSERT(!_lastConnVert || (_lastConnVert->lstNext == _firstShapeVert)); \ + COLA_ASSERT((!_firstConnVert && !_lastConnVert) || \ (_firstConnVert && _lastConnVert) ); \ - assert((!_firstShapeVert && !_lastShapeVert) || \ + COLA_ASSERT((!_firstShapeVert && !_lastShapeVert) || \ (_firstShapeVert && _lastShapeVert) ); \ - assert(!_firstShapeVert || _firstShapeVert->id.isShape); \ - assert(!_lastShapeVert || _lastShapeVert->id.isShape); \ - assert(!_firstConnVert || !(_firstConnVert->id.isShape)); \ - assert(!_lastConnVert || !(_lastConnVert->id.isShape)); \ + COLA_ASSERT(!_firstShapeVert || _firstShapeVert->id.isShape); \ + COLA_ASSERT(!_lastShapeVert || _lastShapeVert->id.isShape); \ + COLA_ASSERT(!_firstConnVert || !(_firstConnVert->id.isShape)); \ + COLA_ASSERT(!_lastConnVert || !(_lastConnVert->id.isShape)); \ } while(0) void VertInfList::addVertex(VertInf *vert) { checkVertInfListConditions(); - assert(vert->lstPrev == NULL); - assert(vert->lstNext == NULL); + COLA_ASSERT(vert->lstPrev == NULL); + COLA_ASSERT(vert->lstNext == NULL); if (!(vert->id.isShape)) { @@ -329,10 +369,18 @@ void VertInfList::addVertex(VertInf *vert) } -void VertInfList::removeVertex(VertInf *vert) +// Removes a vertex from the list and returns a pointer to the vertex +// following the removed one. +VertInf *VertInfList::removeVertex(VertInf *vert) { + if (vert == NULL) + { + return NULL; + } // Conditions for correct data structure checkVertInfListConditions(); + + VertInf *following = vert->lstNext; if (!(vert->id.isShape)) { @@ -421,6 +469,50 @@ void VertInfList::removeVertex(VertInf *vert) vert->lstNext = NULL; checkVertInfListConditions(); + + return following; +} + + +VertInf *VertInfList::getVertexByID(const VertID& id) +{ + VertID searchID = id; + if (searchID.vn == kUnassignedVertexNumber) + { + unsigned int topbit = ((unsigned int) 1) << 31; + if (searchID.objID & topbit) + { + searchID.objID = searchID.objID & ~topbit; + searchID.vn = VertID::src; + } + else + { + searchID.vn = VertID::tar; + } + } + VertInf *last = end(); + for (VertInf *curr = connsBegin(); curr != last; curr = curr->lstNext) + { + if (curr->id == searchID) + { + return curr; + } + } + return NULL; +} + + +VertInf *VertInfList::getVertexByPos(const Point& p) +{ + VertInf *last = end(); + for (VertInf *curr = shapesBegin(); curr != last; curr = curr->lstNext) + { + if (curr->point == p) + { + return curr; + } + } + return NULL; } @@ -447,6 +539,18 @@ VertInf *VertInfList::end(void) } +unsigned int VertInfList::connsSize(void) const +{ + return _connVertices; +} + + +unsigned int VertInfList::shapesSize(void) const +{ + return _shapeVertices; +} + + } diff --git a/src/libavoid/vertices.h b/src/libavoid/vertices.h index 1b0dcf3aa..b07c87f95 100644 --- a/src/libavoid/vertices.h +++ b/src/libavoid/vertices.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef AVOID_VERTICES_H #define AVOID_VERTICES_H @@ -28,6 +31,7 @@ #include <map> #include <iostream> #include <cstdio> + #include "libavoid/geomtypes.h" namespace Avoid { @@ -37,16 +41,18 @@ class Router; typedef std::list<EdgeInf *> EdgeInfList; +typedef unsigned int ConnDirFlags; + class VertID { public: unsigned int objID; bool isShape; - int vn; + unsigned short vn; - static const int src; - static const int tar; + static const unsigned short src; + static const unsigned short tar; VertID(); VertID(unsigned int id, bool s, int n); @@ -64,12 +70,23 @@ class VertID }; +// An ID given to all dummy vertices inserted to allow creation of the +// orthogonal visibility graph since the vertices in the orthogonal graph +// mostly do not correspond to shape corners or connector endpoints. +// +static const VertID dummyOrthogID(0, true, 0); + + class VertInf { public: - VertInf(Router *router, const VertID& vid, const Point& vpoint); + VertInf(Router *router, const VertID& vid, const Point& vpoint, + const bool addToRouter = true); + ~VertInf(); + void Reset(const VertID& vid, const Point& vpoint); void Reset(const Point& vpoint); void removeFromGraph(const bool isConnVert = true); + bool orphaned(void); Router *_router; VertID id; @@ -80,28 +97,40 @@ class VertInf VertInf *shNext; EdgeInfList visList; unsigned int visListSize; + EdgeInfList orthogVisList; + unsigned int orthogVisListSize; EdgeInfList invisList; unsigned int invisListSize; VertInf *pathNext; - double pathDist; + ConnDirFlags visDirections; }; bool directVis(VertInf *src, VertInf *dst); +// A linked list of all the vertices in the router instance. All the +// connector endpoints are listed first, then all the shape vertices. +// Dunnny vertices inserted for orthogonal routing are classed as shape +// vertices but have VertID(0, 0). +// class VertInfList { public: VertInfList(); void addVertex(VertInf *vert); - void removeVertex(VertInf *vert); + VertInf *removeVertex(VertInf *vert); + VertInf *getVertexByID(const VertID& id); + VertInf *getVertexByPos(const Point& p); VertInf *shapesBegin(void); VertInf *connsBegin(void); VertInf *end(void); - void stats(void) + unsigned int connsSize(void) const; + unsigned int shapesSize(void) const; + void stats(FILE *fp = stderr) { - printf("Conns %d, shapes %d\n", _connVertices, _shapeVertices); + fprintf(fp, "Conns %d, shapes %d\n", _connVertices, + _shapeVertices); } private: VertInf *_firstShapeVert; diff --git a/src/libavoid/viscluster.cpp b/src/libavoid/viscluster.cpp new file mode 100644 index 000000000..a127c5a17 --- /dev/null +++ b/src/libavoid/viscluster.cpp @@ -0,0 +1,96 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2004-2008 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + + +#include "libavoid/viscluster.h" +#include "libavoid/router.h" +#include "libavoid/assertions.h" + + +namespace Avoid { + + +ClusterRef::ClusterRef(Router *router, unsigned int id, Polygon& ply) + : _router(router) + , _poly(ply, router) + , _active(false) +{ + _id = router->assignId(id); +} + + +ClusterRef::~ClusterRef() +{ +} + + +void ClusterRef::makeActive(void) +{ + COLA_ASSERT(!_active); + + // Add to connRefs list. + _pos = _router->clusterRefs.insert(_router->clusterRefs.begin(), this); + + _active = true; +} + + +void ClusterRef::makeInactive(void) +{ + COLA_ASSERT(_active); + + // Remove from connRefs list. + _router->clusterRefs.erase(_pos); + + _active = false; +} + + +void ClusterRef::setNewPoly(Polygon& poly) +{ + _poly = ReferencingPolygon(poly, _router); +} + + +unsigned int ClusterRef::id(void) +{ + return _id; +} + + +ReferencingPolygon& ClusterRef::polygon(void) +{ + return _poly; +} + + +Router *ClusterRef::router(void) +{ + return _router; +} + + +} + + diff --git a/src/libavoid/viscluster.h b/src/libavoid/viscluster.h new file mode 100644 index 000000000..5827e5070 --- /dev/null +++ b/src/libavoid/viscluster.h @@ -0,0 +1,67 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2004-2008 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> +*/ + + +#ifndef AVOID_CLUSTER_H +#define AVOID_CLUSTER_H + +#include <list> + +#include "libavoid/geometry.h" + + +namespace Avoid { + +class Router; +class ClusterRef; +typedef std::list<ClusterRef *> ClusterRefList; + + +class ClusterRef +{ + public: + ClusterRef(Router *router, unsigned int id, Polygon& poly); + ~ClusterRef(); + void setNewPoly(Polygon& poly); + unsigned int id(void); + ReferencingPolygon& polygon(void); + Router *router(void); + void makeActive(void); + void makeInactive(void); + + private: + Router *_router; + unsigned int _id; + ReferencingPolygon _poly; + bool _active; + ClusterRefList::iterator _pos; +}; + + +} + + +#endif + + diff --git a/src/libavoid/visibility.cpp b/src/libavoid/visibility.cpp index d2b057643..089911f35 100644 --- a/src/libavoid/visibility.cpp +++ b/src/libavoid/visibility.cpp @@ -2,26 +2,39 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2009 Monash University + * + * -------------------------------------------------------------------- + * The Visibility Sweep technique is based upon the method described + * in Section 5.2 of: + * Lee, D.-T. (1978). Proximity and reachability in the plane., + * PhD thesis, Department of Electrical Engineering, + * University of Illinois, Urbana, IL. + * -------------------------------------------------------------------- * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #include <algorithm> #include <cfloat> +#define _USE_MATH_DEFINES +#include <cmath> #include "libavoid/shape.h" #include "libavoid/debug.h" @@ -30,8 +43,7 @@ #include "libavoid/graph.h" #include "libavoid/geometry.h" #include "libavoid/router.h" - -#include <math.h> +#include "libavoid/assertions.h" #ifdef LINEDEBUG #include "SDL_gfxPrimitives.h" @@ -53,16 +65,7 @@ void shapeVis(ShapeRef *shape) VertInf *shapeBegin = shape->firstVert(); VertInf *shapeEnd = shape->lastVert()->lstNext; - VertInf *pointsBegin = NULL; - if (router->IncludeEndpoints) - { - pointsBegin = router->vertices.connsBegin(); - } - else - { - pointsBegin = router->vertices.shapesBegin(); - } - + VertInf *pointsBegin = router->vertices.connsBegin(); for (VertInf *curr = shapeBegin; curr != shapeEnd; curr = curr->lstNext) { bool knownNew = true; @@ -73,6 +76,11 @@ void shapeVis(ShapeRef *shape) db_printf("\tFirst Half:\n"); for (VertInf *j = pointsBegin ; j != curr; j = j->lstNext) { + if (j->id == dummyOrthogID) + { + // Don't include orthogonal dummy vertices. + continue; + } EdgeInf::checkEdgeVisibility(curr, j, knownNew); } @@ -80,6 +88,11 @@ void shapeVis(ShapeRef *shape) VertInf *pointsEnd = router->vertices.end(); for (VertInf *k = shapeEnd; k != pointsEnd; k = k->lstNext) { + if (k->id == dummyOrthogID) + { + // Don't include orthogonal dummy vertices. + continue; + } EdgeInf::checkEdgeVisibility(curr, k, knownNew); } } @@ -113,7 +126,7 @@ void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, const VertID& pID = point->id; // Make sure we're only doing ptVis for endpoints. - assert(!(pID.isShape)); + COLA_ASSERT(!(pID.isShape)); if ( !(router->InvisibilityGrph) ) { @@ -135,9 +148,14 @@ void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, for (VertInf *k = router->vertices.shapesBegin(); k != shapesEnd; k = k->lstNext) { + if (k->id == dummyOrthogID) + { + // Don't include orthogonal dummy vertices. + continue; + } EdgeInf::checkEdgeVisibility(point, k, knownNew); } - if (router->IncludeEndpoints && partner) + if (partner) { EdgeInf::checkEdgeVisibility(point, partner, knownNew); } @@ -152,7 +170,6 @@ void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, static VertInf *centerInf; static Point centerPoint; static VertID centerID; -static double centerAngle; class PointPair @@ -165,19 +182,44 @@ class PointPair double y = vInf->point.y - centerPoint.y; angle = pos_to_angle(x, y); + distance = euclideanDist(centerPoint, vInf->point); } - bool operator==(const PointPair& rhs) const + bool operator<(const PointPair& rhs) const { - if (vInf->id == rhs.vInf->id) + // Firstly order by angle. + if (angle == rhs.angle) { - return true; + // If the points are collinear, then order them in increasing + // distance from the point we are sweeping around. + if (distance == rhs.distance) + { + // XXX: Add this assertion back if we require that + // connector endpoints have unique IDs. For the + // moment it is okay for them to have the same ID. + //COLA_ASSERT(vInf->id != rhs.vInf->id); + + // If comparing two points at the same physical + // position, then order them by their VertIDs. + return vInf->id < rhs.vInf->id; + } + return distance < rhs.distance; } - return false; + return angle < rhs.angle; } static double pos_to_angle(double x, double y) { + if (y == 0) + { + return ((x < 0) ? 180 : 0); + } + else if (x == 0) + { + return ((y < 0) ? 270 : 90); + } + double ang = atan(y / x); ang = (ang * 180) / M_PI; + if (x < 0) { ang += 180; @@ -186,36 +228,48 @@ class PointPair { ang += 360; } + COLA_ASSERT(ang >= 0); + COLA_ASSERT(ang <= 360); return ang; } VertInf *vInf; double angle; + double distance; }; -typedef std::list<PointPair > VertList; +typedef std::set<PointPair > VertSet; class EdgePair { public: - EdgePair(VertInf *v1, VertInf *v2, double d, double a) - : vInf1(v1), vInf2(v2), initdist(d), initangle(a) + EdgePair() : + vInf1(NULL), vInf2(NULL), dist1(0.0), dist2(0.0), angle(0.0), + angleDist(0.0) + { + // The default constuctor should never be called. + // This is defined to appease the MSVC compiler. + COLA_ASSERT(false); + } + EdgePair(const PointPair& p1, VertInf *v) : + vInf1(p1.vInf), + vInf2(v), + dist1(p1.distance), + dist2(euclideanDist(vInf2->point, centerPoint)), + angle(p1.angle), + angleDist(p1.distance) { - currdist = initdist; - currangle = initangle; } bool operator<(const EdgePair& rhs) const { - if (initdist == rhs.initdist) + COLA_ASSERT(angle == rhs.angle); + if (angleDist == rhs.angleDist) { - // TODO: This is a bit of a hack, should be - // set by the call to the constructor. - return dist(centerPoint, vInf2->point) < - dist(centerPoint, rhs.vInf2->point); + return (dist2 < rhs.dist2); } - return (initdist < rhs.initdist); + return (angleDist < rhs.angleDist); } bool operator==(const EdgePair& rhs) const { @@ -239,37 +293,53 @@ class EdgePair } return true; } - void SetObsAng(double a) + void setNegativeAngle(void) { - obsAngle = fmod(initangle - (a - 180), 360); + angle = -1.0; + } + double setCurrAngle(const PointPair& p) + { + if (p.vInf->point == vInf1->point) + { + angleDist = dist1; + angle = p.angle; + } + else if (p.vInf->point == vInf2->point) + { + angleDist = dist2; + angle = p.angle; + } + else if (p.angle != angle) + { + COLA_ASSERT(p.angle > angle); + angle = p.angle; + Point pp; + int result = rayIntersectPoint(vInf1->point, vInf2->point, + centerPoint, p.vInf->point, &(pp.x), &(pp.y)); + if (result != DO_INTERSECT) + { + // This can happen with points that appear to have the + // same angle but at are at slightly different positions + angleDist = std::min(dist1, dist2); + } + else + { + angleDist = euclideanDist(pp, centerPoint); + } + } - //db_printf("SetObsAng: %.2f (from init %.2f, a %.2f)\n", - // obsAngle, initangle, a); + return angleDist; } VertInf *vInf1; VertInf *vInf2; - double initdist; - double initangle; - double currdist; - double currangle; - double obsAngle; + double dist1; + double dist2; + double angle; + double angleDist; }; -typedef std::set<EdgePair> EdgeSet; - - -static bool ppCompare(PointPair& pp1, PointPair& pp2) -{ - if (pp1.angle == pp2.angle) - { - // If the points are colinear, then order them in increasing - // distance from the point we are sweeping around. - return dist(centerPoint, pp1.vInf->point) < - dist(centerPoint, pp2.vInf->point); - } - return pp1.angle < pp2.angle; -} +typedef std::list<EdgePair> SweepEdgeList; #define AHEAD 1 @@ -278,11 +348,11 @@ static bool ppCompare(PointPair& pp1, PointPair& pp2) class isBoundingShape { public: - // constructor remembers the value provided - isBoundingShape(ShapeSet& set) - : ss(set) + // Class instance remembers the ShapeSet. + isBoundingShape(ShapeSet& set) : + ss(set) { } - // the following is an overloading of the function call operator + // The following is an overloading of the function call operator. bool operator () (const PointPair& pp) { if (pp.vInf->id.isShape && @@ -293,58 +363,111 @@ class isBoundingShape return false; } private: + // MSVC wants to generate the assignment operator and the default + // constructor, but fails. Therefore we declare them private and + // don't implement them. + isBoundingShape & operator=(isBoundingShape const &); + isBoundingShape(); + ShapeSet& ss; }; -static bool sweepVisible(EdgeSet& T, VertInf *currInf, VertInf *lastInf, - bool lastVisible, double lastAngle, int *blocker) +static bool sweepVisible(SweepEdgeList& T, const PointPair& point, + std::set<unsigned int>& onBorderIDs, int *blocker) { + if (T.empty()) + { + // No blocking edges. + return true; + } - if (!lastInf || (lastAngle != centerAngle)) + Router *router = point.vInf->_router; + bool visible = true; + + SweepEdgeList::const_iterator closestIt = T.begin(); + SweepEdgeList::const_iterator end = T.end(); + while (closestIt != end) { - // Nothing before it on the current ray - EdgeSet::iterator closestIt = T.begin(); - if (closestIt != T.end()) + if ((point.vInf->point == closestIt->vInf1->point) || + (point.vInf->point == closestIt->vInf2->point)) { + // If the ray intersects just the endpoint of a + // blocking edge then ignore that edge. + ++closestIt; + continue; + } + break; + } + if (closestIt == end) + { + return true; + } - Point &e1 = (*closestIt).vInf1->point; - Point &e2 = (*closestIt).vInf2->point; - - if (segmentIntersect(centerInf->point, currInf->point, e1, e2)) + if (! point.vInf->id.isShape ) + { + // It's a connector endpoint, so we have to ignore + // edges of containing shapes for determining visibility. + ShapeSet& rss = router->contains[point.vInf->id]; + while (closestIt != end) + { + if (rss.find(closestIt->vInf1->id.objID) == rss.end()) { - *blocker = (*closestIt).vInf1->id.objID; - return false; + // This is not a containing edge so do the normal + // test and then stop. + if (point.distance > closestIt->angleDist) + { + visible = false; + } + else if ((point.distance == closestIt->angleDist) && + onBorderIDs.find(closestIt->vInf1->id.objID) != + onBorderIDs.end()) + { + // Touching, but centerPoint is on another edge of + // shape shape, so count as blocking. + visible = false; + } + break; } + // This was a containing edge, so consider the next along. + ++closestIt; } } else { - // There was another point before this on the ray (lastInf) - if (!lastVisible) + // Just test to see if this point is closer than the closest + // edge blocking this ray. + if (point.distance > closestIt->angleDist) { - *blocker = -1; - return false; + visible = false; } - else + else if ((point.distance == closestIt->angleDist) && + onBorderIDs.find(closestIt->vInf1->id.objID) != + onBorderIDs.end()) { - // Check if there is an edge in T that blocks the ray - // between lastInf and currInf. - EdgeSet::iterator tfin = T.end(); - for (EdgeSet::iterator l = T.begin(); l != tfin; ++l) - { - Point &e1 = (*l).vInf1->point; - Point &e2 = (*l).vInf2->point; + // Touching, but centerPoint is on another edge of + // shape shape, so count as blocking. + visible = false; + } + } - if (segmentIntersect(lastInf->point, currInf->point, e1, e2)) - { - *blocker = (*l).vInf1->id.objID; - return false; - } - } + if (!visible) + { + *blocker = (*closestIt).vInf1->id.objID; +#ifdef LINEDEBUG + Point &e1 = (*closestIt).vInf1->point; + Point &e2 = (*closestIt).vInf2->point; + + if (router->avoid_screen) + { + int canx = 151; + int cany = 55; + lineRGBA(router->avoid_screen, e1.x + canx, e1.y + cany, + e2.x + canx, e2.y + cany, 0, 0, 225, 255); } +#endif } - return true; + return visible; } @@ -358,120 +481,128 @@ void vertexSweep(VertInf *vert) centerID = pID; centerPoint = pPoint; Point centerPt = pPoint; - centerAngle = -1; // List of shape (and maybe endpt) vertices, except p // Sort list, around - VertList v; + VertSet v; // Initialise the vertex list + ShapeSet& ss = router->contains[centerID]; VertInf *beginVert = router->vertices.connsBegin(); VertInf *endVert = router->vertices.end(); for (VertInf *inf = beginVert; inf != endVert; inf = inf->lstNext) { - if (inf->id == centerID) + if (inf == centerInf) + { + // Don't include the center point itself. + continue; + } + else if (inf->id == dummyOrthogID) { - // Don't include the center point + // Don't include orthogonal dummy vertices. + continue; + } + + if (!(centerID.isShape) && (ss.find(inf->id.objID) != ss.end())) + { + // Don't include edge points of containing shapes. + unsigned int shapeID = inf->id.objID; + db_printf("Center is inside shape %u so ignore shape edges.\n", + shapeID); continue; } if (inf->id.isShape) { - // Add shape vertex - v.push_back(inf); + // Add shape vertex. + v.insert(inf); } else { - if (router->IncludeEndpoints) + // Add connector endpoint. + if (centerID.isShape) { - if (centerID.isShape) - { - // Add endpoint vertex - v.push_back(inf); - } - else + // Center is a shape vertex, so add all endpoint vertices. + v.insert(inf); + } + else + { + // Center is an endpoint, so only include the other + // endpoint from the matching connector. + VertID partnerID = VertID(centerID.objID, false, + (centerID.vn == 1) ? 2 : 1); + if (inf->id == partnerID) { - // Center is an endpoint, so only include the other - // endpoint from the matching connector. - VertID partnerID = VertID(centerID.objID, false, - (centerID.vn == 1) ? 2 : 1); - if (inf->id == partnerID) - { - v.push_back(inf); - } + v.insert(inf); } } } } - // TODO: This should be done with a sorted data type and insertion sort. - v.sort(ppCompare); - - EdgeSet e; - ShapeSet& ss = router->contains[centerID]; + std::set<unsigned int> onBorderIDs; - // And edge to T that intersect the initial ray. - VertInf *last = router->vertices.end(); - for (VertInf *k = router->vertices.shapesBegin(); k != last; ) + // Add edges to T that intersect the initial ray. + SweepEdgeList e; + VertSet::const_iterator vbegin = v.begin(); + VertSet::const_iterator vend = v.end(); + for (VertSet::const_iterator t = vbegin; t != vend; ++t) { - VertID kID = k->id; - if (!(centerID.isShape) && (ss.find(kID.objID) != ss.end())) - { - unsigned int shapeID = kID.objID; - db_printf("Center is inside shape %u so ignore shape edges.\n", - shapeID); - // One of the endpoints is inside this shape so ignore it. - while ((k != last) && (k->id.objID == shapeID)) - { - // And skip the other vertices from this shape. - k = k->lstNext; - } - continue; - } + VertInf *k = t->vInf; - VertInf *kPrev = k->shPrev; - if ((centerInf == k) || (centerInf == kPrev)) - { - k = k->lstNext; - continue; - } + COLA_ASSERT(centerInf != k); + COLA_ASSERT(centerID.isShape || (ss.find(k->id.objID) == ss.end())); Point xaxis(DBL_MAX, centerInf->point.y); - if (segmentIntersect(centerInf->point, xaxis, kPrev->point, k->point)) + VertInf *kPrev = k->shPrev; + VertInf *kNext = k->shNext; + if (kPrev && (kPrev != centerInf) && + (vecDir(centerInf->point, xaxis, kPrev->point) == AHEAD)) { - double distance; - if (vecDir(centerInf->point, xaxis, kPrev->point) == BEHIND) + if (segmentIntersect(centerInf->point, xaxis, kPrev->point, + k->point)) { - distance = dist(centerInf->point, kPrev->point); + EdgePair intPair = EdgePair(*t, kPrev); + e.push_back(intPair); } - else + if ((vecDir(kPrev->point, k->point, centerInf->point) == 0) && + inBetween(kPrev->point, k->point, centerInf->point)) { - distance = dist(centerInf->point, k->point); + // Record that centerPoint is on an obstacle line. + onBorderIDs.insert(k->id.objID); + } + } + else if (kNext && (kNext != centerInf) && + (vecDir(centerInf->point, xaxis, kNext->point) == AHEAD)) + { + if (segmentIntersect(centerInf->point, xaxis, kNext->point, + k->point)) + { + EdgePair intPair = EdgePair(*t, kNext); + e.push_back(intPair); + } + if ((vecDir(kNext->point, k->point, centerInf->point) == 0) && + inBetween(kNext->point, k->point, centerInf->point)) + { + // Record that centerPoint is on an obstacle line. + onBorderIDs.insert(k->id.objID); } - - EdgePair intPair = EdgePair(k, kPrev, distance, 0.0); - e.insert(intPair).first; } - k = k->lstNext; } + for (SweepEdgeList::iterator c = e.begin(); c != e.end(); ++c) + { + (*c).setNegativeAngle(); + } + // Start the actual sweep. db_printf("SWEEP: "); centerID.db_print(); db_printf("\n"); - VertInf *lastInf = NULL; - double lastAngle = 0; - bool lastVisible = false; - int lastBlocker = 0; - - isBoundingShape isBounding(router->contains[centerID]); - VertList::iterator vfst = v.begin(); - VertList::iterator vfin = v.end(); - for (VertList::iterator t = vfst; t != vfin; ++t) + isBoundingShape isBounding(ss); + for (VertSet::const_iterator t = vbegin; t != vend; ++t) { VertInf *currInf = (*t).vInf; VertID& currID = currInf->id; Point& currPt = currInf->point; - centerAngle = (*t).angle; #ifdef LINEDEBUG Sint16 ppx = (int) centerPt.x; @@ -479,29 +610,28 @@ void vertexSweep(VertInf *vert) Sint16 cx = (int) currPt.x; Sint16 cy = (int) currPt.y; + + int canx = 151; + int cany = 55; #endif - double currDist = dist(centerPt, currPt); - db_printf("Dist: %.1f.\n", currDist); + const double& currDist = (*t).distance; EdgeInf *edge = EdgeInf::existingEdge(centerInf, currInf); if (edge == NULL) { edge = new EdgeInf(centerInf, currInf); } - // Ignore vertices from bounding shapes, if sweeping round an endpoint. - if (!(centerID.isShape) && isBounding(*t)) + + for (SweepEdgeList::iterator c = e.begin(); c != e.end(); ++c) { - if (router->InvisibilityGrph) - { - // if p and t can't see each other, add blank edge - db_printf("\tSkipping visibility edge... \n\t\t"); - edge->addBlocker(currInf->id.objID); - edge->db_print(); - } - continue; + (*c).setCurrAngle(*t); } + e.sort(); + // Check visibility. + int blocker = 0; + bool currVisible = sweepVisible(e, *t, onBorderIDs, &blocker); bool cone1 = true, cone2 = true; if (centerID.isShape) @@ -519,7 +649,6 @@ void vertexSweep(VertInf *vert) if (!cone1 || !cone2) { - lastInf = NULL; if (router->InvisibilityGrph) { db_printf("\tSetting invisibility edge... \n\t\t"); @@ -529,18 +658,15 @@ void vertexSweep(VertInf *vert) } else { - int blocker = 0; - // Check visibility. - bool currVisible = sweepVisible(e, currInf, - lastInf, lastVisible, lastAngle, &blocker); - if (blocker == -1) - { - blocker = lastBlocker; - } if (currVisible) { #ifdef LINEDEBUG - lineRGBA(avoid_screen, ppx, ppy, cx, cy, 255, 0, 0, 32); + if (router->avoid_screen) + { + lineRGBA(router->avoid_screen, ppx + canx, ppy + cany, + cx + canx, cy + cany, 255, 0, 0, 75); + SDL_Delay(1000); + } #endif db_printf("\tSetting visibility edge... \n\t\t"); edge->setDist(currDist); @@ -552,72 +678,55 @@ void vertexSweep(VertInf *vert) edge->addBlocker(blocker); edge->db_print(); } - - lastVisible = currVisible; - lastInf = currInf; - lastAngle = centerAngle; - lastBlocker = blocker; + } + + if (!(edge->added()) && !(router->InvisibilityGrph)) + { + delete edge; + edge = NULL; } if (currID.isShape) { // This is a shape edge - Point& prevPt = currInf->shPrev->point; - Point& nextPt = currInf->shNext->point; - - int prevDir = vecDir(centerPt, currPt, prevPt); - EdgePair prevPair = EdgePair(currInf, currInf->shPrev, - currDist, centerAngle); - EdgeSet::iterator ePtr; - if (prevDir == BEHIND) + if (currInf->shPrev != centerInf) { - // XXX: Strangely e.find does not return the correct results. - // ePtr = e.find(prevPair); - ePtr = std::find(e.begin(), e.end(), prevPair); - if (ePtr != e.end()) + Point& prevPt = currInf->shPrev->point; + int prevDir = vecDir(centerPt, currPt, prevPt); + EdgePair prevPair = EdgePair(*t, currInf->shPrev); + + if (prevDir == BEHIND) { - e.erase(ePtr); + e.remove(prevPair); } - } - else if ((prevDir == AHEAD) && (currInf->shPrev != centerInf)) - { - double x = prevPt.x - currPt.x; - double y = prevPt.y - currPt.y; - double angle = PointPair::pos_to_angle(x, y); - prevPair.SetObsAng(angle); - - ePtr = e.insert(prevPair).first; - } - - - int nextDir = vecDir(centerPt, currPt, nextPt); - EdgePair nextPair = EdgePair(currInf, currInf->shNext, - currDist, centerAngle); - - if (nextDir == BEHIND) - { - // XXX: Strangely e.find does not return the correct results. - // ePtr = e.find(nextPair); - ePtr = std::find(e.begin(), e.end(), nextPair); - if (ePtr != e.end()) + else if (prevDir == AHEAD) { - e.erase(ePtr); + e.push_front(prevPair); } } - else if ((nextDir == AHEAD) && (currInf->shNext != centerInf)) + + if (currInf->shNext != centerInf) { - double x = nextPt.x - currPt.x; - double y = nextPt.y - currPt.y; - double angle = PointPair::pos_to_angle(x, y); - nextPair.SetObsAng(angle); + Point& nextPt = currInf->shNext->point; + int nextDir = vecDir(centerPt, currPt, nextPt); + EdgePair nextPair = EdgePair(*t, currInf->shNext); - ePtr = e.insert(nextPair).first; + if (nextDir == BEHIND) + { + e.remove(nextPair); + } + else if (nextDir == AHEAD) + { + e.push_front(nextPair); + } } } - #ifdef LINEDEBUG - SDL_Flip(avoid_screen); + if (router->avoid_screen) + { + SDL_Flip(router->avoid_screen); + } #endif } } diff --git a/src/libavoid/visibility.h b/src/libavoid/visibility.h index dd68ac692..92e56d922 100644 --- a/src/libavoid/visibility.h +++ b/src/libavoid/visibility.h @@ -2,24 +2,27 @@ * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router - * Copyright (C) 2004-2006 Michael Wybrow <mjwybrow@users.sourceforge.net> + * + * Copyright (C) 2004-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * Author(s): Michael Wybrow <mjwybrow@users.sourceforge.net> */ + #ifndef AVOID_VISIBILITY_H #define AVOID_VISIBILITY_H @@ -28,7 +31,9 @@ namespace Avoid { - +class ShapeRef; +class VertInf; + extern void vertexVisibility(VertInf *point, VertInf *partner, bool knownNew, const bool gen_contains = false); extern void vertexSweep(VertInf *point); diff --git a/src/libavoid/vpsc.cpp b/src/libavoid/vpsc.cpp new file mode 100644 index 000000000..19d360375 --- /dev/null +++ b/src/libavoid/vpsc.cpp @@ -0,0 +1,1301 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2005-2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Tim Dwyer <Tim.Dwyer@csse.monash.edu.au> + * + * -------------- + * + * This file contains a slightly modified version of Solver() from libvpsc: + * A solver for the problem of Variable Placement with Separation Constraints. + * It has the following changes from the Adaptagrams VPSC version: + * - The required VPSC code has been consolidated into a single file. + * - Unnecessary code (like Solver) has been removed. + * - The PairingHeap code has been replaced by a STL priority_queue. + * + * Modifications: Michael Wybrow <mjwybrow@users.sourceforge.net> + * +*/ + +#include <iostream> +#include <math.h> +#include <sstream> +#include <map> +#include <cfloat> +#include <cstdio> + +#include "libavoid/vpsc.h" +#include "libavoid/assertions.h" + + +using namespace std; + +namespace Avoid { + +static const double ZERO_UPPERBOUND=-1e-10; +static const double LAGRANGIAN_TOLERANCE=-1e-4; + +IncSolver::IncSolver(vector<Variable*> const &vs, vector<Constraint *> const &cs) + : m(cs.size()), + cs(cs), + n(vs.size()), + vs(vs) +{ + for(unsigned i=0;i<n;++i) { + vs[i]->in.clear(); + vs[i]->out.clear(); + } + for(unsigned i=0;i<m;++i) { + Constraint *c=cs[i]; + c->left->out.push_back(c); + c->right->in.push_back(c); + } + bs=new Blocks(vs); +#ifdef LIBVPSC_LOGGING + printBlocks(); + //COLA_ASSERT(!constraintGraphIsCyclic(n,vs)); +#endif + + inactive=cs; + for(Constraints::iterator i=inactive.begin();i!=inactive.end();++i) { + (*i)->active=false; + } +} +IncSolver::~IncSolver() { + delete bs; +} + +// useful in debugging +void IncSolver::printBlocks() { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + for(set<Block*>::iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + f<<" "<<*b<<endl; + } + for(unsigned i=0;i<m;i++) { + f<<" "<<*cs[i]<<endl; + } +#endif +} + +/* + * Stores the relative positions of the variables in their finalPosition + * field. + */ +void IncSolver::copyResult() { + for(Variables::const_iterator i=vs.begin();i!=vs.end();++i) { + Variable* v=*i; + v->finalPosition=v->position(); + COLA_ASSERT(v->finalPosition==v->finalPosition); + } +} + +struct node { + set<node*> in; + set<node*> out; +}; +// useful in debugging - cycles would be BAD +bool IncSolver::constraintGraphIsCyclic(const unsigned n, Variable* const vs[]) { + map<Variable*, node*> varmap; + vector<node*> graph; + for(unsigned i=0;i<n;i++) { + node *u=new node; + graph.push_back(u); + varmap[vs[i]]=u; + } + for(unsigned i=0;i<n;i++) { + for(vector<Constraint*>::iterator c=vs[i]->in.begin();c!=vs[i]->in.end();++c) { + Variable *l=(*c)->left; + varmap[vs[i]]->in.insert(varmap[l]); + } + + for(vector<Constraint*>::iterator c=vs[i]->out.begin();c!=vs[i]->out.end();++c) { + Variable *r=(*c)->right; + varmap[vs[i]]->out.insert(varmap[r]); + } + } + while(graph.size()>0) { + node *u=NULL; + vector<node*>::iterator i=graph.begin(); + for(;i!=graph.end();++i) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set<node*>::iterator j=u->out.begin();j!=u->out.end();++j) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i<graph.size(); ++i) { + delete graph[i]; + } + return false; +} + +// useful in debugging - cycles would be BAD +bool IncSolver::blockGraphIsCyclic() { + map<Block*, node*> bmap; + vector<node*> graph; + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + node *u=new node; + graph.push_back(u); + bmap[b]=u; + } + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + b->setUpInConstraints(); + Constraint *c=b->findMinInConstraint(); + while(c!=NULL) { + Block *l=c->left->block; + bmap[b]->in.insert(bmap[l]); + b->deleteMinInConstraint(); + c=b->findMinInConstraint(); + } + + b->setUpOutConstraints(); + c=b->findMinOutConstraint(); + while(c!=NULL) { + Block *r=c->right->block; + bmap[b]->out.insert(bmap[r]); + b->deleteMinOutConstraint(); + c=b->findMinOutConstraint(); + } + } + while(graph.size()>0) { + node *u=NULL; + vector<node*>::iterator i=graph.begin(); + for(;i!=graph.end();++i) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set<node*>::iterator j=u->out.begin();j!=u->out.end();++j) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i<graph.size(); i++) { + delete graph[i]; + } + return false; +} + +bool IncSolver::solve() { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"solve_inc()..."<<endl; +#endif + satisfy(); + double lastcost = DBL_MAX, cost = bs->cost(); + while(fabs(lastcost-cost)>0.0001) { + satisfy(); + lastcost=cost; + cost = bs->cost(); +#ifdef LIBVPSC_LOGGING + f<<" bs->size="<<bs->size()<<", cost="<<cost<<endl; +#endif + } + copyResult(); + return bs->size()!=n; +} +/* + * incremental version of satisfy that allows refinement after blocks are + * moved. + * + * - move blocks to new positions + * - repeatedly merge across most violated constraint until no more + * violated constraints exist + * + * Note: there is a special case to handle when the most violated constraint + * is between two variables in the same block. Then, we must split the block + * over an active constraint between the two variables. We choose the + * constraint with the most negative lagrangian multiplier. + */ +bool IncSolver::satisfy() { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"satisfy_inc()..."<<endl; +#endif + splitBlocks(); + //long splitCtr = 0; + Constraint* v = NULL; + //CBuffer buffer(inactive); + while((v = mostViolated(inactive)) + && (v->equality || ((v->slack() < ZERO_UPPERBOUND) && !v->active))) + { + COLA_ASSERT(!v->active); + Block *lb = v->left->block, *rb = v->right->block; + if(lb != rb) { + lb->merge(rb,v); + } else { + if(lb->isActiveDirectedPathBetween(v->right,v->left)) { + // cycle found, relax the violated, cyclic constraint + v->unsatisfiable=true; + continue; + //UnsatisfiableException e; + //lb->getActiveDirectedPathBetween(e.path,v->right,v->left); + //e.path.push_back(v); + //throw e; + } + //if(splitCtr++>10000) { + //throw "Cycle Error!"; + //} + // constraint is within block, need to split first + try { + Constraint* splitConstraint + =lb->splitBetween(v->left,v->right,lb,rb); + if(splitConstraint!=NULL) { + COLA_ASSERT(!splitConstraint->active); + inactive.push_back(splitConstraint); + } else { + v->unsatisfiable=true; + continue; + } + } catch(UnsatisfiableException e) { + e.path.push_back(v); + std::cerr << "Unsatisfiable:" << std::endl; + for(std::vector<Constraint*>::iterator r=e.path.begin(); + r!=e.path.end();++r) + { + std::cerr << **r <<std::endl; + } + v->unsatisfiable=true; + continue; + } + if(v->slack()>=0) { + COLA_ASSERT(!v->active); + // v was satisfied by the above split! + inactive.push_back(v); + bs->insert(lb); + bs->insert(rb); + } else { + bs->insert(lb->merge(rb,v)); + } + } + bs->cleanup(); +#ifdef LIBVPSC_LOGGING + f<<"...remaining blocks="<<bs->size()<<", cost="<<bs->cost()<<endl; +#endif + } +#ifdef LIBVPSC_LOGGING + f<<" finished merges."<<endl; +#endif + bs->cleanup(); + bool activeConstraints=false; + for(unsigned i=0;i<m;i++) { + v=cs[i]; + if(v->active) activeConstraints=true; + if(v->slack() < ZERO_UPPERBOUND) { + ostringstream s; + s<<"Unsatisfied constraint: "<<*v; +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<s.str()<<endl; +#endif + throw s.str().c_str(); + } + } +#ifdef LIBVPSC_LOGGING + f<<" finished cleanup."<<endl; + printBlocks(); +#endif + copyResult(); + return activeConstraints; +} +void IncSolver::moveBlocks() { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"moveBlocks()..."<<endl; +#endif + for(set<Block*>::const_iterator i(bs->begin());i!=bs->end();++i) { + Block *b = *i; + b->updateWeightedPosition(); + //b->posn = b->wposn / b->weight; + } +#ifdef LIBVPSC_LOGGING + f<<" moved blocks."<<endl; +#endif +} +void IncSolver::splitBlocks() { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); +#endif + moveBlocks(); + splitCnt=0; + // Split each block if necessary on min LM + for(set<Block*>::const_iterator i(bs->begin());i!=bs->end();++i) { + Block* b = *i; + Constraint* v=b->findMinLM(); + if(v!=NULL && v->lm < LAGRANGIAN_TOLERANCE) { + COLA_ASSERT(!v->equality); +#ifdef LIBVPSC_LOGGING + f<<" found split point: "<<*v<<" lm="<<v->lm<<endl; +#endif + splitCnt++; + Block *b = v->left->block, *l=NULL, *r=NULL; + COLA_ASSERT(v->left->block == v->right->block); + //double pos = b->posn; + b->split(l,r,v); + //l->posn=r->posn=pos; + //l->wposn = l->posn * l->weight; + //r->wposn = r->posn * r->weight; + l->updateWeightedPosition(); + r->updateWeightedPosition(); + bs->insert(l); + bs->insert(r); + b->deleted=true; + COLA_ASSERT(!v->active); + inactive.push_back(v); +#ifdef LIBVPSC_LOGGING + f<<" new blocks: "<<*l<<" and "<<*r<<endl; +#endif + } + } + //if(splitCnt>0) { std::cout<<" splits: "<<splitCnt<<endl; } +#ifdef LIBVPSC_LOGGING + f<<" finished splits."<<endl; +#endif + bs->cleanup(); +} + +/* + * Scan constraint list for the most violated constraint, or the first equality + * constraint + */ +Constraint* IncSolver::mostViolated(Constraints &l) { + double minSlack = DBL_MAX; + Constraint* v=NULL; +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Looking for most violated..."<<endl; +#endif + Constraints::iterator end = l.end(); + Constraints::iterator deletePoint = end; + for(Constraints::iterator i=l.begin();i!=end;++i) { + Constraint *c=*i; + double slack = c->slack(); + if(c->equality || slack < minSlack) { + minSlack=slack; + v=c; + deletePoint=i; + if(c->equality) break; + } + } + // Because the constraint list is not order dependent we just + // move the last element over the deletePoint and resize + // downwards. There is always at least 1 element in the + // vector because of search. + // TODO check this logic and add parens: + if((deletePoint != end) && ((minSlack < ZERO_UPPERBOUND) && !v->active || v->equality)) { + *deletePoint = l[l.size()-1]; + l.resize(l.size()-1); + } +#ifdef LIBVPSC_LOGGING + f<<" most violated is: "<<*v<<endl; +#endif + return v; +} + + +using std::set; +using std::vector; +using std::iterator; +using std::list; +using std::copy; +#define __NOTNAN(p) (p)==(p) + +long blockTimeCtr; + +Blocks::Blocks(vector<Variable*> const &vs) : vs(vs),nvs(vs.size()) { + blockTimeCtr=0; + for(int i=0;i<nvs;i++) { + insert(new Block(vs[i])); + } +} +Blocks::~Blocks(void) +{ + blockTimeCtr=0; + for(set<Block*>::iterator i=begin();i!=end();++i) { + delete *i; + } + clear(); +} + +/* + * returns a list of variables with total ordering determined by the constraint + * DAG + */ +list<Variable*> *Blocks::totalOrder() { + list<Variable*> *order = new list<Variable*>; + for(int i=0;i<nvs;i++) { + vs[i]->visited=false; + } + for(int i=0;i<nvs;i++) { + if(vs[i]->in.size()==0) { + dfsVisit(vs[i],order); + } + } + return order; +} +// Recursive depth first search giving total order by pushing nodes in the DAG +// onto the front of the list when we finish searching them +void Blocks::dfsVisit(Variable *v, list<Variable*> *order) { + v->visited=true; + vector<Constraint*>::iterator it=v->out.begin(); + for(;it!=v->out.end();++it) { + Constraint *c=*it; + if(!c->right->visited) { + dfsVisit(c->right, order); + } + } +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" order="<<*v<<endl; +#endif + order->push_front(v); +} +/* + * Processes incoming constraints, most violated to least, merging with the + * neighbouring (left) block until no more violated constraints are found + */ +void Blocks::mergeLeft(Block *r) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"mergeLeft called on "<<*r<<endl; +#endif + r->timeStamp=++blockTimeCtr; + r->setUpInConstraints(); + Constraint *c=r->findMinInConstraint(); + while (c != NULL && c->slack()<0) { +#ifdef LIBVPSC_LOGGING + f<<"mergeLeft on constraint: "<<*c<<endl; +#endif + r->deleteMinInConstraint(); + Block *l = c->left->block; + if (l->in==NULL) l->setUpInConstraints(); + double dist = c->right->offset - c->left->offset - c->gap; + if (r->vars->size() < l->vars->size()) { + dist=-dist; + std::swap(l, r); + } + blockTimeCtr++; + r->merge(l, c, dist); + r->mergeIn(l); + r->timeStamp=blockTimeCtr; + removeBlock(l); + c=r->findMinInConstraint(); + } +#ifdef LIBVPSC_LOGGING + f<<"merged "<<*r<<endl; +#endif +} +/* + * Symmetrical to mergeLeft + */ +void Blocks::mergeRight(Block *l) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"mergeRight called on "<<*l<<endl; +#endif + l->setUpOutConstraints(); + Constraint *c = l->findMinOutConstraint(); + while (c != NULL && c->slack()<0) { +#ifdef LIBVPSC_LOGGING + f<<"mergeRight on constraint: "<<*c<<endl; +#endif + l->deleteMinOutConstraint(); + Block *r = c->right->block; + r->setUpOutConstraints(); + double dist = c->left->offset + c->gap - c->right->offset; + if (l->vars->size() > r->vars->size()) { + dist=-dist; + std::swap(l, r); + } + l->merge(r, c, dist); + l->mergeOut(r); + removeBlock(r); + c=l->findMinOutConstraint(); + } +#ifdef LIBVPSC_LOGGING + f<<"merged "<<*l<<endl; +#endif +} +void Blocks::removeBlock(Block *doomed) { + doomed->deleted=true; + //erase(doomed); +} +void Blocks::cleanup() { + vector<Block*> bcopy(begin(),end()); + for(vector<Block*>::iterator i=bcopy.begin();i!=bcopy.end();++i) { + Block *b=*i; + if(b->deleted) { + erase(b); + delete b; + } + } +} +/* + * Splits block b across constraint c into two new blocks, l and r (c's left + * and right sides respectively) + */ +void Blocks::split(Block *b, Block *&l, Block *&r, Constraint *c) { + b->split(l,r,c); +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Split left: "<<*l<<endl; + f<<"Split right: "<<*r<<endl; +#endif + r->posn = b->posn; + //COLA_ASSERT(r->weight!=0); + //r->wposn = r->posn * r->weight; + mergeLeft(l); + // r may have been merged! + r = c->right->block; + r->updateWeightedPosition(); + //r->posn = r->wposn / r->weight; + mergeRight(r); + removeBlock(b); + + insert(l); + insert(r); + COLA_ASSERT(__NOTNAN(l->posn)); + COLA_ASSERT(__NOTNAN(r->posn)); +} +/* + * returns the cost total squared distance of variables from their desired + * positions + */ +double Blocks::cost() { + double c = 0; + for(set<Block*>::iterator i=begin();i!=end();++i) { + c += (*i)->cost(); + } + return c; +} + +void PositionStats::addVariable(Variable* v) { + double ai=scale/v->scale; + double bi=v->offset/v->scale; + double wi=v->weight; + AB+=wi*ai*bi; + AD+=wi*ai*v->desiredPosition; + A2+=wi*ai*ai; + /* +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f << "adding v[" << v->id << "], blockscale=" << scale << ", despos=" + << v->desiredPosition << ", ai=" << ai << ", bi=" << bi + << ", AB=" << AB << ", AD=" << AD << ", A2=" << A2; +#endif +*/ +} +void Block::addVariable(Variable* v) { + v->block=this; + vars->push_back(v); + if(ps.A2==0) ps.scale=v->scale; + //weight+= v->weight; + //wposn += v->weight * (v->desiredPosition - v->offset); + //posn=wposn/weight; + ps.addVariable(v); + posn=(ps.AD - ps.AB) / ps.A2; + COLA_ASSERT(__NOTNAN(posn)); + /* +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f << ", posn=" << posn << endl; +#endif +*/ +} +Block::Block(Variable* const v) + : vars(new vector<Variable*>) + , posn(0) + //, weight(0) + //, wposn(0) + , deleted(false) + , timeStamp(0) + , in(NULL) + , out(NULL) +{ + if(v!=NULL) { + v->offset=0; + addVariable(v); + } +} + +void Block::updateWeightedPosition() { + //wposn=0; + ps.AB=ps.AD=ps.A2=0; + for (Vit v=vars->begin();v!=vars->end();++v) { + //wposn += ((*v)->desiredPosition - (*v)->offset) * (*v)->weight; + ps.addVariable(*v); + } + posn=(ps.AD - ps.AB) / ps.A2; + COLA_ASSERT(__NOTNAN(posn)); +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f << ", posn=" << posn << endl; +#endif +} +Block::~Block(void) +{ + delete vars; + delete in; + delete out; +} +void Block::setUpInConstraints() { + setUpConstraintHeap(in,true); +} +void Block::setUpOutConstraints() { + setUpConstraintHeap(out,false); +} +void Block::setUpConstraintHeap(Heap* &h,bool in) { + delete h; + h = new Heap(); + for (Vit i=vars->begin();i!=vars->end();++i) { + Variable *v=*i; + vector<Constraint*> *cs=in?&(v->in):&(v->out); + for (Cit j=cs->begin();j!=cs->end();++j) { + Constraint *c=*j; + c->timeStamp=blockTimeCtr; + if (((c->left->block != this) && in) || ((c->right->block != this) && !in)) { + h->push(c); + } + } + } +} +Block* Block::merge(Block* b, Constraint* c) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" merging on: "<<*c<<",c->left->offset="<<c->left->offset<<",c->right->offset="<<c->right->offset<<endl; +#endif + double dist = c->right->offset - c->left->offset - c->gap; + Block *l=c->left->block; + Block *r=c->right->block; + if (l->vars->size() < r->vars->size()) { + r->merge(l,c,dist); + } else { + l->merge(r,c,-dist); + } + Block* mergeBlock=b->deleted?this:b; +#ifdef LIBVPSC_LOGGING + f<<" merged block="<<*mergeBlock<<endl; +#endif + return mergeBlock; +} +/* + * Merges b into this block across c. Can be either a + * right merge or a left merge + * @param b block to merge into this + * @param c constraint being merged + * @param distance separation required to satisfy c + */ +void Block::merge(Block *b, Constraint *c, double dist) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" merging: "<<*b<<"dist="<<dist<<endl; +#endif + c->active=true; + //wposn+=b->wposn-dist*b->weight; + //weight+=b->weight; + for(Vit i=b->vars->begin();i!=b->vars->end();++i) { + Variable *v=*i; + //v->block=this; + //vars->push_back(v); + v->offset+=dist; + addVariable(v); + } +#ifdef LIBVPSC_LOGGING + for(Vit i=vars->begin();i!=vars->end();++i) { + Variable *v=*i; + f<<" v["<<v->id<<"]: d="<<v->desiredPosition + <<" a="<<v->scale<<" o="<<v->offset + <<endl; + } + f<<" AD="<<ps.AD<<" AB="<<ps.AB<<" A2="<<ps.A2<<endl; +#endif + //posn=wposn/weight; + //COLA_ASSERT(wposn==ps.AD - ps.AB); + posn=(ps.AD - ps.AB) / ps.A2; + COLA_ASSERT(__NOTNAN(posn)); + b->deleted=true; +} + +void Block::mergeIn(Block *b) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" merging constraint heaps... "<<endl; +#endif + // We check the top of the heaps to remove possible internal constraints + findMinInConstraint(); + b->findMinInConstraint(); + while (!b->in->empty()) + { + in->push(b->in->top()); + b->in->pop(); + } +#ifdef LIBVPSC_LOGGING + f<<" merged heap: "<<*in<<endl; +#endif +} +void Block::mergeOut(Block *b) { + findMinOutConstraint(); + b->findMinOutConstraint(); + while (!b->out->empty()) + { + out->push(b->out->top()); + b->out->pop(); + } +} +Constraint *Block::findMinInConstraint() { + Constraint *v = NULL; + vector<Constraint*> outOfDate; + while (!in->empty()) { + v = in->top(); + Block *lb=v->left->block; + Block *rb=v->right->block; + // rb may not be this if called between merge and mergeIn +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" checking constraint ... "<<*v; + f<<" timestamps: left="<<lb->timeStamp<<" right="<<rb->timeStamp<<" constraint="<<v->timeStamp<<endl; +#endif + if(lb == rb) { + // constraint has been merged into the same block +#ifdef LIBVPSC_LOGGING + if(v->slack()<0) { + f<<" violated internal constraint found! "<<*v<<endl; + f<<" lb="<<*lb<<endl; + f<<" rb="<<*rb<<endl; + } +#endif + in->pop(); +#ifdef LIBVPSC_LOGGING + f<<" ... skipping internal constraint"<<endl; +#endif + } else if(v->timeStamp < lb->timeStamp) { + // block at other end of constraint has been moved since this + in->pop(); + outOfDate.push_back(v); +#ifdef LIBVPSC_LOGGING + f<<" reinserting out of date (reinsert later)"<<endl; +#endif + } else { + break; + } + } + for(Cit i=outOfDate.begin();i!=outOfDate.end();++i) { + v=*i; + v->timeStamp=blockTimeCtr; + in->push(v); + } + if(in->empty()) { + v=NULL; + } else { + v=in->top(); + } + return v; +} +Constraint *Block::findMinOutConstraint() { + if(out->empty()) return NULL; + Constraint *v = out->top(); + while (v->left->block == v->right->block) { + out->pop(); + if(out->empty()) return NULL; + v = out->top(); + } + return v; +} +void Block::deleteMinInConstraint() { + in->pop(); +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"deleteMinInConstraint... "<<endl; + f<<" result: "<<*in<<endl; +#endif +} +void Block::deleteMinOutConstraint() { + out->pop(); +} +inline bool Block::canFollowLeft(Constraint const* c, Variable const* last) const { + return c->left->block==this && c->active && last!=c->left; +} +inline bool Block::canFollowRight(Constraint const* c, Variable const* last) const { + return c->right->block==this && c->active && last!=c->right; +} + +// computes the derivative of v and the lagrange multipliers +// of v's out constraints (as the recursive sum of those below. +// Does not backtrack over u. +// also records the constraint with minimum lagrange multiplier +// in min_lm +double Block::compute_dfdv(Variable* const v, Variable* const u, + Constraint *&min_lm) { + double dfdv=v->dfdv(); + for(Cit it=v->out.begin();it!=v->out.end();++it) { + Constraint *c=*it; + if(canFollowRight(c,u)) { + c->lm=compute_dfdv(c->right,v,min_lm); + dfdv+=c->lm*c->left->scale; + if(!c->equality&&(min_lm==NULL||c->lm<min_lm->lm)) min_lm=c; + } + } + for(Cit it=v->in.begin();it!=v->in.end();++it) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { + c->lm=-compute_dfdv(c->left,v,min_lm); + dfdv-=c->lm*c->right->scale; + if(!c->equality&&(min_lm==NULL||c->lm<min_lm->lm)) min_lm=c; + } + } + return dfdv/v->scale; +} +double Block::compute_dfdv(Variable* const v, Variable* const u) { + double dfdv = v->dfdv(); + for(Cit it = v->out.begin(); it != v->out.end(); ++it) { + Constraint *c = *it; + if(canFollowRight(c,u)) { + c->lm = compute_dfdv(c->right,v); + dfdv += c->lm * c->left->scale; + } + } + for(Cit it=v->in.begin();it!=v->in.end();++it) { + Constraint *c = *it; + if(canFollowLeft(c,u)) { + c->lm = - compute_dfdv(c->left,v); + dfdv -= c->lm * c->right->scale; + } + } + return dfdv/v->scale; +} + +// The top level v and r are variables between which we want to find the +// constraint with the smallest lm. +// Similarly, m is initially NULL and is only assigned a value if the next +// variable to be visited is r or if a possible min constraint is returned from +// a nested call (rather than NULL). +// Then, the search for the m with minimum lm occurs as we return from +// the recursion (checking only constraints traversed left-to-right +// in order to avoid creating any new violations). +// We also do not consider equality constraints as potential split points +bool Block::split_path( + Variable* r, + Variable* const v, + Variable* const u, + Constraint* &m, + bool desperation=false + ) +{ + for(Cit it(v->in.begin());it!=v->in.end();++it) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" left split path: "<<*c<<endl; +#endif + if(c->left==r) { + if(desperation&&!c->equality) m=c; + return true; + } else { + if(split_path(r,c->left,v,m)) { + if(desperation && !c->equality && (!m||c->lm<m->lm)) { + m=c; + } + return true; + } + } + } + } + for(Cit it(v->out.begin());it!=v->out.end();++it) { + Constraint *c=*it; + if(canFollowRight(c,u)) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" right split path: "<<*c<<endl; +#endif + if(c->right==r) { + if(!c->equality) m=c; + return true; + } else { + if(split_path(r,c->right,v,m)) { + if(!c->equality && (!m||c->lm<m->lm)) + m=c; + return true; + } + } + } + } + return false; +} +/* +Block::Pair Block::compute_dfdv_between( + Variable* r, Variable* const v, Variable* const u, + const Direction dir = NONE, bool changedDirection = false) { + double dfdv=v->weight*(v->position() - v->desiredPosition); + Constraint *m=NULL; + for(Cit it(v->in.begin());it!=v->in.end();++it) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { + if(dir==RIGHT) { + changedDirection = true; + } + if(c->left==r) { + r=NULL; + if(!c->equality) m=c; + } + Pair p=compute_dfdv_between(r,c->left,v, + LEFT,changedDirection); + dfdv -= c->lm = -p.first; + if(r && p.second) + m = p.second; + } + } + for(Cit it(v->out.begin());it!=v->out.end();++it) { + Constraint *c=*it; + if(canFollowRight(c,u)) { + if(dir==LEFT) { + changedDirection = true; + } + if(c->right==r) { + r=NULL; + if(!c->equality) m=c; + } + Pair p=compute_dfdv_between(r,c->right,v, + RIGHT,changedDirection); + dfdv += c->lm = p.first; + if(r && p.second) + m = changedDirection && !c->equality && c->lm < p.second->lm + ? c + : p.second; + } + } + return Pair(dfdv,m); +} +*/ + +// resets LMs for all active constraints to 0 by +// traversing active constraint tree starting from v, +// not back tracking over u +void Block::reset_active_lm(Variable* const v, Variable* const u) { + for(Cit it=v->out.begin();it!=v->out.end();++it) { + Constraint *c=*it; + if(canFollowRight(c,u)) { + c->lm=0; + reset_active_lm(c->right,v); + } + } + for(Cit it=v->in.begin();it!=v->in.end();++it) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { + c->lm=0; + reset_active_lm(c->left,v); + } + } +} +void Block::list_active(Variable* const v, Variable* const u) { + for(Cit it=v->out.begin();it!=v->out.end();++it) { + Constraint *c=*it; + if(canFollowRight(c,u)) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" "<<*c<<endl; +#endif + list_active(c->right,v); + } + } + for(Cit it=v->in.begin();it!=v->in.end();++it) { + Constraint *c=*it; + if(canFollowLeft(c,u)) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" "<<*c<<endl; +#endif + list_active(c->left,v); + } + } +} +/* + * finds the constraint with the minimum lagrange multiplier, that is, the constraint + * that most wants to split + */ +Constraint *Block::findMinLM() { + Constraint *min_lm=NULL; + reset_active_lm(vars->front(),NULL); + compute_dfdv(vars->front(),NULL,min_lm); +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" langrangians: "<<endl; + list_active(vars->front(),NULL); +#endif + return min_lm; +} +Constraint *Block::findMinLMBetween(Variable* const lv, Variable* const rv) { + reset_active_lm(vars->front(),NULL); + compute_dfdv(vars->front(),NULL); + Constraint *min_lm=NULL; + split_path(rv,lv,NULL,min_lm); +#if 0 + if(min_lm==NULL) { + split_path(rv,lv,NULL,min_lm,true); + } +#else + if(min_lm==NULL) { + fprintf(stderr,"Couldn't find split point!\n"); + UnsatisfiableException e; + getActivePathBetween(e.path,lv,rv,NULL); + throw e; + } + COLA_ASSERT(min_lm!=NULL); +#endif + return min_lm; +} + +// populates block b by traversing the active constraint tree adding variables as they're +// visited. Starts from variable v and does not backtrack over variable u. +void Block::populateSplitBlock(Block *b, Variable* v, Variable const* u) { + b->addVariable(v); + for (Cit c=v->in.begin();c!=v->in.end();++c) { + if (canFollowLeft(*c,u)) + populateSplitBlock(b, (*c)->left, v); + } + for (Cit c=v->out.begin();c!=v->out.end();++c) { + if (canFollowRight(*c,u)) + populateSplitBlock(b, (*c)->right, v); + } +} +/* + * Returns the active path between variables u and v... not back tracking over w + */ +bool Block::getActivePathBetween(Constraints& path, Variable const* u, + Variable const* v, Variable const *w) const { + if(u==v) return true; + for (Cit_const c=u->in.begin();c!=u->in.end();++c) { + if (canFollowLeft(*c,w)) { + if(getActivePathBetween(path, (*c)->left, v, u)) { + path.push_back(*c); + return true; + } + } + } + for (Cit_const c=u->out.begin();c!=u->out.end();++c) { + if (canFollowRight(*c,w)) { + if(getActivePathBetween(path, (*c)->right, v, u)) { + path.push_back(*c); + return true; + } + } + } + return false; +} +// Search active constraint tree from u to see if there is a directed path to v. +// Returns true if path is found with all constraints in path having their visited flag +// set true. +bool Block::isActiveDirectedPathBetween(Variable const* u, Variable const* v) const { + if(u==v) return true; + for (Cit_const c=u->out.begin();c!=u->out.end();++c) { + if(canFollowRight(*c,NULL)) { + if(isActiveDirectedPathBetween((*c)->right,v)) { + return true; + } + } + } + return false; +} +bool Block::getActiveDirectedPathBetween( + Constraints& path, Variable const* u, Variable const* v) const { + if(u==v) return true; + for (Cit_const c=u->out.begin();c!=u->out.end();++c) { + if(canFollowRight(*c,NULL)) { + if(getActiveDirectedPathBetween(path,(*c)->right,v)) { + path.push_back(*c); + return true; + } + } + } + return false; +} +/* + * Block needs to be split because of a violated constraint between vl and vr. + * We need to search the active constraint tree between l and r and find the constraint + * with min lagrangrian multiplier and split at that point. + * Returns the split constraint + */ +Constraint* Block::splitBetween(Variable* const vl, Variable* const vr, + Block* &lb, Block* &rb) { +#ifdef LIBVPSC_LOGGING + ofstream f(LOGFILE,ios::app); + f<<" need to split between: "<<*vl<<" and "<<*vr<<endl; +#endif + Constraint *c=findMinLMBetween(vl, vr); +#ifdef LIBVPSC_LOGGING + f<<" going to split on: "<<*c<<endl; +#endif + if(c!=NULL) { + split(lb,rb,c); + deleted = true; + } + return c; +} + +/* + * Creates two new blocks, l and r, and splits this block across constraint c, + * placing the left subtree of constraints (and associated variables) into l + * and the right into r. + */ +void Block::split(Block* &l, Block* &r, Constraint* c) { + c->active=false; + l=new Block(); + populateSplitBlock(l,c->left,c->right); + //COLA_ASSERT(l->weight>0); + r=new Block(); + populateSplitBlock(r,c->right,c->left); + //COLA_ASSERT(r->weight>0); +} + +/* + * Computes the cost (squared euclidean distance from desired positions) of the + * current positions for variables in this block + */ +double Block::cost() { + double c = 0; + for (Vit v=vars->begin();v!=vars->end();++v) { + double diff = (*v)->position() - (*v)->desiredPosition; + c += (*v)->weight * diff * diff; + } + return c; +} +ostream& operator <<(ostream &os, const Block& b) +{ + os<<"Block(posn="<<b.posn<<"):"; + for(Block::Vit v=b.vars->begin();v!=b.vars->end();++v) { + os<<" "<<**v; + } + if(b.deleted) { + os<<" Deleted!"; + } + return os; +} + +Constraint::Constraint(Variable *left, Variable *right, double gap, bool equality) +: left(left), + right(right), + gap(gap), + timeStamp(0), + active(false), + equality(equality), + unsatisfiable(false) +{ + // In hindsight I think it's probably better to build the constraint DAG + // (by creating variable in/out lists) when needed, rather than in advance + //left->out.push_back(this); + //right->in.push_back(this); +} +Constraint::~Constraint() { + // see constructor: the following is just way too slow. + // Better to create a + // new DAG on demand than maintain the lists dynamically. + //Constraints::iterator i; + //for(i=left->out.begin(); i!=left->out.end(); i++) { + //if(*i==this) break; + //} + //left->out.erase(i); + //for(i=right->in.begin(); i!=right->in.end(); i++) { + //if(*i==this) break; + //} + //right->in.erase(i); +} +double Constraint::slack() const { + return unsatisfiable ? DBL_MAX + : right->scale * right->position() + - gap - left->scale * left->position(); +} +std::ostream& operator <<(std::ostream &os, const Constraint &c) +{ + if(&c==NULL) { + os<<"NULL"; + } else { + const char *type=c.equality?"=":"<="; + std::ostringstream lscale, rscale; + if(c.left->scale!=1) { + lscale << c.left->scale << "*"; + } + if(c.right->scale!=1) { + rscale << c.right->scale << "*"; + } + os<<lscale.str()<<*c.left<<"+"<<c.gap<<type<<rscale.str()<<*c.right; + if(c.left->block&&c.right->block) + os<<"("<<c.slack()<<")"<<(c.active?"-active":"") + <<"(lm="<<c.lm<<")"; + else + os<<"(vars have no position)"; + } + return os; +} + +bool CompareConstraints::operator() ( + Constraint *const &l, Constraint *const &r +) const { + double const sl = + l->left->block->timeStamp > l->timeStamp + ||l->left->block==l->right->block + ?-DBL_MAX:l->slack(); + double const sr = + r->left->block->timeStamp > r->timeStamp + ||r->left->block==r->right->block + ?-DBL_MAX:r->slack(); + if(sl==sr) { + // arbitrary choice based on id + if(l->left->id==r->left->id) { + if(l->right->id<r->right->id) return true; + return false; + } + if(l->left->id<r->left->id) return true; + return false; + } + return sl > sr; +} + +std::ostream& operator <<(std::ostream &os, const Variable &v) { + if(v.block) + os << "(" << v.id << "=" << v.position() << ")"; + else + os << "(" << v.id << "=" << v.desiredPosition << ")"; + return os; +} + +} diff --git a/src/libavoid/vpsc.h b/src/libavoid/vpsc.h new file mode 100644 index 000000000..4d6d8ce61 --- /dev/null +++ b/src/libavoid/vpsc.h @@ -0,0 +1,255 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2005-2009 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Tim Dwyer <Tim.Dwyer@csse.monash.edu.au> + * + * -------------- + * + * This file contains a slightly modified version of Solver() from libvpsc: + * A solver for the problem of Variable Placement with Separation Constraints. + * It has the following changes from the Adaptagrams VPSC version: + * - The required VPSC code has been consolidated into a single file. + * - Unnecessary code (like Solver) has been removed. + * - The PairingHeap code has been replaced by a STL priority_queue. + * + * Modifications: Michael Wybrow <mjwybrow@users.sourceforge.net> + * +*/ + +#ifndef LIBAVOID_VPSC_H +#define LIBAVOID_VPSC_H + +#include <vector> +#include <list> +#include <set> +#include <queue> + +namespace Avoid { + +class Variable; +class Constraint; +typedef std::vector<Variable*> Variables; +typedef std::vector<Constraint*> Constraints; +class CompareConstraints { +public: + bool operator() (Constraint *const &l, Constraint *const &r) const; +}; +struct PositionStats { + PositionStats() : scale(0), AB(0), AD(0), A2(0) {} + void addVariable(Variable* const v); + double scale; + double AB; + double AD; + double A2; +}; + +typedef std::priority_queue<Constraint*,std::vector<Constraint*>, + CompareConstraints> Heap; + +class Block +{ + typedef Variables::iterator Vit; + typedef Constraints::iterator Cit; + typedef Constraints::const_iterator Cit_const; + + friend std::ostream& operator <<(std::ostream &os,const Block &b); +public: + Variables *vars; + double posn; + //double weight; + //double wposn; + PositionStats ps; + Block(Variable* const v=NULL); + ~Block(void); + Constraint* findMinLM(); + Constraint* findMinLMBetween(Variable* const lv, Variable* const rv); + Constraint* findMinInConstraint(); + Constraint* findMinOutConstraint(); + void deleteMinInConstraint(); + void deleteMinOutConstraint(); + void updateWeightedPosition(); + void merge(Block *b, Constraint *c, double dist); + Block* merge(Block *b, Constraint *c); + void mergeIn(Block *b); + void mergeOut(Block *b); + void split(Block *&l, Block *&r, Constraint *c); + Constraint* splitBetween(Variable* vl, Variable* vr, Block* &lb, Block* &rb); + void setUpInConstraints(); + void setUpOutConstraints(); + double cost(); + bool deleted; + long timeStamp; + Heap *in; + Heap *out; + bool getActivePathBetween(Constraints& path, Variable const* u, + Variable const* v, Variable const *w) const; + bool isActiveDirectedPathBetween( + Variable const* u, Variable const* v) const; + bool getActiveDirectedPathBetween(Constraints& path, Variable const * u, Variable const * v) const; +private: + typedef enum {NONE, LEFT, RIGHT} Direction; + typedef std::pair<double, Constraint*> Pair; + void reset_active_lm(Variable* const v, Variable* const u); + void list_active(Variable* const v, Variable* const u); + double compute_dfdv(Variable* const v, Variable* const u); + double compute_dfdv(Variable* const v, Variable* const u, Constraint *&min_lm); + bool split_path(Variable*, Variable* const, Variable* const, + Constraint* &min_lm, bool desperation); + bool canFollowLeft(Constraint const* c, Variable const* last) const; + bool canFollowRight(Constraint const* c, Variable const* last) const; + void populateSplitBlock(Block *b, Variable* v, Variable const* u); + void addVariable(Variable* v); + void setUpConstraintHeap(Heap* &h,bool in); +}; + + +class Constraint; +typedef std::vector<Constraint*> Constraints; +class Variable +{ + friend std::ostream& operator <<(std::ostream &os, const Variable &v); + friend class Block; + friend class Constraint; + friend class IncSolver; +public: + int id; // useful in log files + double desiredPosition; + double finalPosition; + double weight; // how much the variable wants to + // be at it's desired position + double scale; // translates variable to another space + double offset; + Block *block; + bool visited; + bool fixedDesiredPosition; + Constraints in; + Constraints out; + char *toString(); + inline Variable(const int id, const double desiredPos=-1.0, + const double weight=1.0, const double scale=1.0) + : id(id) + , desiredPosition(desiredPos) + , weight(weight) + , scale(scale) + , offset(0) + , block(NULL) + , visited(false) + , fixedDesiredPosition(false) + { + } + double dfdv() const { + return 2. * weight * ( position() - desiredPosition ); + } +private: + double position() const { + return (block->ps.scale*block->posn+offset)/scale; + } +}; + + +class Constraint +{ + friend std::ostream& operator <<(std::ostream &os,const Constraint &c); +public: + Variable *left; + Variable *right; + double gap; + double lm; + Constraint(Variable *left, Variable *right, double gap, bool equality=false); + ~Constraint(); + double slack() const; + long timeStamp; + bool active; + const bool equality; + bool unsatisfiable; +}; +/* + * A block structure defined over the variables such that each block contains + * 1 or more variables, with the invariant that all constraints inside a block + * are satisfied by keeping the variables fixed relative to one another + */ +class Blocks : public std::set<Block*> +{ +public: + Blocks(Variables const &vs); + ~Blocks(void); + void mergeLeft(Block *r); + void mergeRight(Block *l); + void split(Block *b, Block *&l, Block *&r, Constraint *c); + std::list<Variable*> *totalOrder(); + void cleanup(); + double cost(); +private: + void dfsVisit(Variable *v, std::list<Variable*> *order); + void removeBlock(Block *doomed); + Variables const &vs; + int nvs; +}; + +extern long blockTimeCtr; + +struct UnsatisfiableException { + Constraints path; +}; +struct UnsatisfiedConstraint { + UnsatisfiedConstraint(Constraint& c):c(c) {} + Constraint& c; +}; +/* + * Variable Placement with Separation Constraints problem instance + */ +class IncSolver { +public: + unsigned splitCnt; + bool satisfy(); + bool solve(); + void moveBlocks(); + void splitBlocks(); + IncSolver(Variables const &vs, Constraints const &cs); + + ~IncSolver(); + Variables const & getVariables() { return vs; } +protected: + Blocks *bs; + unsigned m; + Constraints const &cs; + unsigned n; + Variables const &vs; + void printBlocks(); + void copyResult(); +private: + bool constraintGraphIsCyclic(const unsigned n, Variable* const vs[]); + bool blockGraphIsCyclic(); + Constraints inactive; + Constraints violated; + Constraint* mostViolated(Constraints &l); +}; + +struct delete_object +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + + +} + +#endif // AVOID_VPSC_H diff --git a/src/libcroco/cr-cascade.c b/src/libcroco/cr-cascade.c index f389fc746..7fef86c4a 100644 --- a/src/libcroco/cr-cascade.c +++ b/src/libcroco/cr-cascade.c @@ -70,6 +70,7 @@ cr_cascade_new (CRStyleSheet * a_author_sheet, PRIVATE (result) = (CRCascadePriv *)g_try_malloc (sizeof (CRCascadePriv)); if (!PRIVATE (result)) { + g_free(result); cr_utils_trace_info ("Out of memory"); return NULL; } diff --git a/src/libnr/Makefile_insert b/src/libnr/Makefile_insert index 5cd2717be..4b19028f9 100644 --- a/src/libnr/Makefile_insert +++ b/src/libnr/Makefile_insert @@ -5,7 +5,6 @@ libnr_mmx_sources = \ libnr/have_mmx.S \ libnr/nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP.S \ libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_A8_RGBAP.S \ - libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S \ libnr/nr_mmx_R8G8B8_R8G8B8_R8G8B8A8_P.S endif diff --git a/src/libnr/nr-compose-transform.cpp b/src/libnr/nr-compose-transform.cpp index afc8fd987..6e03faf2f 100644 --- a/src/libnr/nr-compose-transform.cpp +++ b/src/libnr/nr-compose-transform.cpp @@ -16,30 +16,25 @@ #include "nr-pixops.h" #include "nr-matrix.h" - -#ifdef WITH_MMX +/*#ifdef WITH_MMX #ifdef __cplusplus extern "C" { -#endif /* __cplusplus */ -/* fixme: */ -int nr_have_mmx (void); -void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs, - const unsigned char *spx, int sw, int sh, int srs, - const long *FFd2s, unsigned int alpha); -void nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs, - const unsigned char *spx, int sw, int sh, int srs, - const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits); +#endif // __cplusplus +/ * fixme: * / +/ *int nr_have_mmx (void); #define NR_PIXOPS_MMX (1 && nr_have_mmx ()) #ifdef __cplusplus } #endif //__cplusplus #endif +*/ /* fixme: Implement missing (Lauris) */ /* fixme: PREMUL colors before calculating average (Lauris) */ /* Fixed point precision */ #define FBITS 12 +#define FBITS_HP 18 // In some places we need a higher precision void nr_R8G8B8A8_N_EMPTY_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int sw, int sh, int srs, @@ -168,10 +163,10 @@ void nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_P_TRANSFORM (unsigned char *px, int w, in static void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int sw, int sh, int srs, - const long *FFd2s, unsigned int alpha) + const long long *FFd2s, unsigned int alpha) { - unsigned char *d0; - int FFsx0, FFsy0; + unsigned char *d0; + long long FFsx0, FFsy0; int x, y; d0 = px; @@ -180,15 +175,15 @@ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h for (y = 0; y < h; y++) { unsigned char *d; - long FFsx, FFsy; + long long FFsx, FFsy; d = d0; FFsx = FFsx0; FFsy = FFsy0; for (x = 0; x < w; x++) { long sx, sy; - sx = FFsx >> FBITS; + sx = long(FFsx >> FBITS_HP); if ((sx >= 0) && (sx < sw)) { - sy = FFsy >> FBITS; + sy = long(FFsy >> FBITS_HP); if ((sy >= 0) && (sy < sh)) { const unsigned char *s; unsigned int a; @@ -224,11 +219,11 @@ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (unsigned char *px, int w, int h static void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h, int rs, const unsigned char *spx, int sw, int sh, int srs, - const long *FFd2s, const long *FF_S, unsigned int alpha, int dbits) + const long long *FFd2s, const long *FF_S, unsigned int alpha, int dbits) { int size; unsigned char *d0; - int FFsx0, FFsy0; + long long FFsx0, FFsy0; int x, y; size = (1 << dbits); @@ -242,7 +237,7 @@ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h for (y = 0; y < h; y++) { unsigned char *d; - long FFsx, FFsy; + long long FFsx, FFsy; d = d0; FFsx = FFsx0; FFsy = FFsy0; @@ -252,9 +247,9 @@ nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (unsigned char *px, int w, int h r = g = b = a = 0; for (i = 0; i < size; i++) { long sx, sy; - sx = (FFsx + FF_S[2 * i]) >> FBITS; + sx = (long (FFsx >> (FBITS_HP - FBITS)) + FF_S[2 * i]) >> FBITS; if ((sx >= 0) && (sx < sw)) { - sy = (FFsy + FF_S[2 * i + 1]) >> FBITS; + sy = (long (FFsy >> (FBITS_HP - FBITS)) + FF_S[2 * i + 1]) >> FBITS; if ((sy >= 0) && (sy < sh)) { const unsigned char *s; unsigned int ca; @@ -302,6 +297,7 @@ void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, in { int dbits; long FFd2s[6]; + long long FFd2s_HP[6]; // with higher precision int i; if (alpha == 0) return; @@ -310,17 +306,11 @@ void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, in for (i = 0; i < 6; i++) { FFd2s[i] = (long) (d2s[i] * (1 << FBITS) + 0.5); + FFd2s_HP[i] = (long long) (d2s[i] * (1 << FBITS_HP) + 0.5);; } if (dbits == 0) { -#ifdef WITH_MMX - if (NR_PIXOPS_MMX) { - /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ - nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha); - return; - } -#endif - nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s, alpha); + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 (px, w, h, rs, spx, sw, sh, srs, FFd2s_HP, alpha); } else { int xsize, ysize; long FFs_x_x_S, FFs_x_y_S, FFs_y_x_S, FFs_y_y_S; @@ -344,14 +334,7 @@ void nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (unsigned char *px, int w, in } } -#ifdef WITH_MMX - if (NR_PIXOPS_MMX) { - /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ - nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits); - return; - } -#endif - nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s, FF_S, alpha, dbits); + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n (px, w, h, rs, spx, sw, sh, srs, FFd2s_HP, FF_S, alpha, dbits); } } diff --git a/src/libnr/nr-compose.cpp b/src/libnr/nr-compose.cpp index 3b99678e2..74f9d036b 100644 --- a/src/libnr/nr-compose.cpp +++ b/src/libnr/nr-compose.cpp @@ -773,6 +773,7 @@ nr_R8G8B8A8_P_EMPTY_A8_RGBA32 (unsigned char *px, int w, int h, int rs, const un c[3] = a; /* WARNING: MMX composer REQUIRES w > 0 and h > 0 */ nr_mmx_R8G8B8A8_P_EMPTY_A8_RGBAP (px, w, h, rs, mpx, mrs, c); + // This mmx optimized code is approx. 2x faster than the non-optimized code below (Measured by Diederik van Lierop, 2009-12-17) return; } #endif diff --git a/src/libnr/nr-point-fns.cpp b/src/libnr/nr-point-fns.cpp index 5082c3a10..0142655f2 100644 --- a/src/libnr/nr-point-fns.cpp +++ b/src/libnr/nr-point-fns.cpp @@ -61,19 +61,6 @@ NR::Point abs(NR::Point const &b) return ret; } -NR::Point * -get_snap_vector (NR::Point p, NR::Point o, double snap, double initial) -{ - double r = NR::L2 (p - o); - if (r < 1e-3) - return NULL; - double angle = NR::atan2 (p - o); - // snap angle to snaps increments, starting from initial: - double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap; - // calculate the new position and subtract p to get the vector: - return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p); -} - NR::Point snap_vector_midpoint (NR::Point p, NR::Point begin, NR::Point end, double snap) { diff --git a/src/libnr/nr-point-fns.h b/src/libnr/nr-point-fns.h index e927725b4..9ef7205c6 100644 --- a/src/libnr/nr-point-fns.h +++ b/src/libnr/nr-point-fns.h @@ -90,8 +90,6 @@ Point abs(Point const &b); } /* namespace NR */ -NR::Point *get_snap_vector (NR::Point p, NR::Point o, double snap, double initial); - NR::Point snap_vector_midpoint (NR::Point p, NR::Point begin, NR::Point end, double snap); double get_offset_between_points (NR::Point p, NR::Point begin, NR::Point end); diff --git a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S b/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S deleted file mode 100644 index e30056af2..000000000 --- a/src/libnr/nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM.S +++ /dev/null @@ -1,414 +0,0 @@ - .file "nr-compose-transform.c" - -# Ensure Inkscape is execshield protected - .section .note.GNU-stack - .previous - - .text - .align 2 -.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 - .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,@function - -/* - * This code is in public domain - * - */ - -nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0: - pushl %ebp - movl %esp, %ebp - pushl %ebx - subl $48, %esp - pushl %edi - pushl %esi - -/* Load %mm7 with [0 0 0 0] */ - movl $0, %eax - movd %eax, %mm7 - -/* Load %mm6 with [128 128 128 128] */ - movl $0x80808080, %eax - movd %eax, %mm6 - punpcklbw %mm7, %mm6 - -/* Load %mm5 with [255 255 255 255] */ - movl $0xffffffff, %eax - movd %eax, %mm5 - punpcklbw %mm7, %mm5 - -/* Load %mm0 with [a a a a] */ - movzbl 44(%ebp), %eax - movd %eax, %mm0 - punpcklwd %mm0, %mm0 - punpckldq %mm0, %mm0 - - movl 8(%ebp), %eax - movl %eax, -8(%ebp) - movl 40(%ebp), %eax - addl $16, %eax - movl (%eax), %eax - movl %eax, -12(%ebp) - movl 40(%ebp), %eax - addl $20, %eax - movl (%eax), %eax - movl %eax, -16(%ebp) - movl $0, -24(%ebp) -.L29: - movl -24(%ebp), %eax - cmpl 16(%ebp), %eax - jl .L32 - jmp .L28 -.L32: - movl -8(%ebp), %edi - - movl -12(%ebp), %eax - movl %eax, %esi - movl -16(%ebp), %eax - movl %eax, -36(%ebp) - - movl 12(%ebp), %ebx -.for_x_0: - - movl %esi, %ecx - cmpl $0, %ecx - js .clip_0 - sarl $12, %ecx - cmpl 28(%ebp), %ecx - jge .clip_0 - shll $2, %ecx - - movl -36(%ebp), %eax - cmpl $0, %eax - js .clip_0 - sarl $12, %eax - cmpl 32(%ebp), %eax - jge .clip_0 - imull 36(%ebp), %eax - - addl %ecx, %eax - addl 24(%ebp), %eax - -/* Fg -> %mm1 */ - movl (%eax), %eax - testl $0xff000000, %eax - jz .clip_0 - movd %eax, %mm1 - punpcklbw %mm7, %mm1 - -/* [a a a 255] -> %mm3 */ - shrl $24, %eax - movl $0x10101, %edx - mull %edx - orl $0xff000000, %eax - movd %eax, %mm3 - punpcklbw %mm7, %mm3 - -/* [Fg * a] -> mm1 */ - pmullw %mm3, %mm1 - paddw %mm6, %mm1 - movq %mm1, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm1 - psrlw $8, %mm1 - -/* Multiply by alpha */ - pmullw %mm0, %mm1 - paddw %mm6, %mm1 - movq %mm1, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm1 - psrlw $8, %mm1 - -/* [255 - FgA] -> mm2 */ - movq %mm1, %mm2 - punpckhwd %mm2, %mm2 - punpckhdq %mm2, %mm2 - pxor %mm5, %mm2 - -/* Bg -> mm3 */ - movd (%edi), %mm3 - punpcklbw %mm7, %mm3 - -/* Fg + ((255 - FgA) * Bg) / 255 */ - - pmullw %mm2, %mm3 - paddw %mm6, %mm3 - movq %mm3, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm3 - psrlw $8, %mm3 - paddw %mm1, %mm3 - -/* Store pixel */ - packuswb %mm3, %mm3 - movd %mm3, (%edi) - -.clip_0: -.L37: - movl 40(%ebp), %ecx - movl (%ecx), %edx - addl %edx, %esi - movl 4(%ecx), %edx - addl %edx, -36(%ebp) - - addl $4, %edi - - decl %ebx - jnz .for_x_0 - -.L34: - movl 8(%ecx), %edx - addl %edx, -12(%ebp) - movl 12(%ecx), %edx - addl %edx, -16(%ebp) - - movl 20(%ebp), %edx - leal -8(%ebp), %eax - addl %edx, (%eax) - leal -24(%ebp), %eax - incl (%eax) - jmp .L29 -.L28: - emms - popl %esi - popl %edi - addl $48, %esp - popl %ebx - popl %ebp - ret -.Lfe2: - .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0,.Lfe2-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_0 - -/* - * - * dbits 52(%ebp) - * alpha 48(%ebp) - * FF_S 44(%ebp) - * - * d -32(%ebp) -> %edi - * i -60(%ebp) -> %esi - * sx -64(%ebp) -> %ebx - * sy -68(%ebp) - * s -72(%ebp) - * - * %mm0 a a a a - * %mm1 FgA - * %mm2 SumFgA - * %mm3 a a a 255 - * %mm4 -*/ - - .align 2 -.globl nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n - .type nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,@function -nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n: - pushl %ebp - movl %esp, %ebp - pushl %ebx - subl $72, %esp - pushl %edi - pushl %esi - -/* Load %mm7 with [0 0 0 0] */ - movl $0, %eax - movd %eax, %mm7 - -/* Load %mm6 with [128 128 128 128] */ - movl $0x80808080, %eax - movd %eax, %mm6 - punpcklbw %mm7, %mm6 - -/* Load %mm5 with [255 255 255 255] */ - movl $0xffffffff, %eax - movd %eax, %mm5 - punpcklbw %mm7, %mm5 - -/* Load %mm0 with [a a a a] */ - movzbl 48(%ebp), %eax - movd %eax, %mm0 - punpcklwd %mm0, %mm0 - punpckldq %mm0, %mm0 - - movl $1, %eax - movzbl 52(%ebp), %ecx - sall %cl, %eax - movl %eax, -8(%ebp) - movl 8(%ebp), %eax - movl %eax, -12(%ebp) - movl 40(%ebp), %eax - addl $16, %eax - movl (%eax), %eax - movl %eax, -16(%ebp) - movl 40(%ebp), %eax - addl $20, %eax - movl (%eax), %eax - movl %eax, -20(%ebp) - movl $0, -28(%ebp) -.L44: - movl -28(%ebp), %eax - cmpl 16(%ebp), %eax - jl .L47 - jmp .exit_n -.L47: - movl -12(%ebp), %eax - movl %eax, -32(%ebp) - movl -16(%ebp), %eax - movl %eax, -36(%ebp) - movl -20(%ebp), %eax - movl %eax, -40(%ebp) - movl $0, -24(%ebp) -.L48: - movl -24(%ebp), %eax - cmpl 12(%ebp), %eax - jl .L51 - jmp .L49 -.L51: - -/* Zero accumulator */ - movq %mm7, %mm2 - -/* Set i to dptr (size - 1) */ - movl -8(%ebp), %esi - sub $1, %esi - shll $3, %esi - - movl 44(%ebp), %edi - movl -36(%ebp), %ecx - -.for_i_n: - movl (%edi,%esi), %ebx - addl %ecx, %ebx -/* Test negative before shift */ - cmpl $0, %ebx - js .next_i_n - sarl $12, %ebx - cmpl 28(%ebp), %ebx - jge .next_i_n -/* We multiply sx by 4 here */ - shll $2, %ebx - - movl 4(%edi,%esi), %eax - addl -40(%ebp), %eax -/* Test negative before shift */ - cmpl $0, %eax - js .next_i_n - sarl $12, %eax - cmpl 32(%ebp), %eax - jge .next_i_n -/* We multiply sy by srs here */ - imull 36(%ebp), %eax - - addl %ebx, %eax - addl 24(%ebp), %eax - -/* Fg -> %mm1 */ - movl (%eax), %eax - testl $0xff000000, %eax - jz .next_i_n - movd %eax, %mm1 - punpcklbw %mm7, %mm1 - -/* [a a a 255] -> %mm3 */ - shrl $24, %eax - movl $0x10101, %edx - mull %edx - orl $0xff000000, %eax - movd %eax, %mm3 - punpcklbw %mm7, %mm3 - -/* [Fg * a] -> mm1 */ - pmullw %mm3, %mm1 - paddw %mm6, %mm1 - movq %mm1, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm1 - psrlw $8, %mm1 - -/* Add to accumulator */ - paddw %mm1, %mm2 - -.next_i_n: - subl $8, %esi - jnb .for_i_n - -/* Divide components by sample size */ - movd 52(%ebp), %mm3 - psrlw %mm3, %mm2 - -/* Multiply by alpha */ - pmullw %mm0, %mm2 - paddw %mm6, %mm2 - movq %mm2, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm2 - psrlw $8, %mm2 - -/* [255 - FgA] -> mm1 */ - movq %mm2, %mm1 - punpckhwd %mm1, %mm1 - punpckhdq %mm1, %mm1 - pxor %mm5, %mm1 - - movl -32(%ebp), %edi -/* Bg -> mm3 */ - movd (%edi), %mm3 - punpcklbw %mm7, %mm3 - -/* Fg + ((255 - FgA) * Bg) / 255 */ - - pmullw %mm1, %mm3 - paddw %mm6, %mm3 - movq %mm3, %mm4 - psrlw $8, %mm4 - paddw %mm4, %mm3 - psrlw $8, %mm3 - paddw %mm2, %mm3 - -/* Store pixel */ - packuswb %mm3, %mm3 - movd %mm3, (%edi) - -.L58: - movl 40(%ebp), %eax - movl (%eax), %edx - leal -36(%ebp), %eax - addl %edx, (%eax) - movl 40(%ebp), %eax - addl $4, %eax - movl (%eax), %edx - leal -40(%ebp), %eax - addl %edx, (%eax) - leal -32(%ebp), %eax - addl $4, (%eax) - leal -24(%ebp), %eax - incl (%eax) - jmp .L48 -.L49: - movl 40(%ebp), %eax - addl $8, %eax - movl (%eax), %edx - leal -16(%ebp), %eax - addl %edx, (%eax) - movl 40(%ebp), %eax - addl $12, %eax - movl (%eax), %edx - leal -20(%ebp), %eax - addl %edx, (%eax) - movl 20(%ebp), %edx - leal -12(%ebp), %eax - addl %edx, (%eax) - leal -28(%ebp), %eax - incl (%eax) - jmp .L44 - -.exit_n: - emms - popl %esi - popl %edi - addl $72, %esp - popl %ebx - popl %ebp - ret -.Lfe3: - .size nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n,.Lfe3-nr_mmx_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM_n - .ident "GCC: (GNU) 3.2" diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp index db7cd9747..a63f70d75 100644 --- a/src/libnrtype/FontFactory.cpp +++ b/src/libnrtype/FontFactory.cpp @@ -26,6 +26,10 @@ /* Freetype2 */ # include <pango/pangoft2.h> +#include <tr1/unordered_map> + + +typedef std::tr1::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType; // need to avoid using the size field size_t font_descr_hash::operator()( PangoFontDescription *const &x) const { @@ -299,20 +303,25 @@ font_factory *font_factory::Default(void) return lUsine; } -font_factory::font_factory(void) -{ - fontSize = 512; - nbEnt = 0; - maxEnt = 32; - ents = (font_entry*)g_malloc(maxEnt*sizeof(font_entry)); +font_factory::font_factory(void) : + nbEnt(0), + maxEnt(32), + ents(static_cast<font_entry*>(g_malloc(maxEnt*sizeof(font_entry)))), #ifdef USE_PANGO_WIN32 - hScreenDC = pango_win32_get_dc(); - fontServer = pango_win32_font_map_for_display(); - fontContext = pango_win32_get_context(); - pangoFontCache = pango_win32_font_map_get_font_cache(fontServer); + fontServer(pango_win32_font_map_for_display()), + fontContext(pango_win32_get_context()), + pangoFontCache(pango_win32_font_map_get_font_cache(fontServer)), + hScreenDC(pango_win32_get_dc()), +#else + fontServer(pango_ft2_font_map_new()), + fontContext(0), +#endif + fontSize(512), + loadedPtr(new FaceMapType()) +{ +#ifdef USE_PANGO_WIN32 #else - fontServer = pango_ft2_font_map_new(); pango_ft2_font_map_set_resolution((PangoFT2FontMap*)fontServer, 72, 72); fontContext = pango_ft2_font_map_create_context((PangoFT2FontMap*)fontServer); pango_ft2_font_map_set_default_substitute((PangoFT2FontMap*)fontServer,FactorySubstituteFunc,this,NULL); @@ -321,6 +330,11 @@ font_factory::font_factory(void) font_factory::~font_factory(void) { + if (loadedPtr) { + FaceMapType* tmp = static_cast<FaceMapType*>(loadedPtr); + loadedPtr = 0; + } + for (int i = 0;i < nbEnt;i++) ents[i].f->Unref(); if ( ents ) g_free(ents); @@ -793,6 +807,7 @@ font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) font_instance *res = NULL; + FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr); if ( loadedFaces.find(descr) == loadedFaces.end() ) { // not yet loaded PangoFont *nFace = NULL; @@ -849,8 +864,9 @@ font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) res->Ref(); AddInCache(res); } - if(res) - res->InitTheFace(); + if (res) { + res->InitTheFace(); + } return res; } @@ -924,15 +940,18 @@ font_instance *font_factory::Face(char const *family, NRTypePosDef apos) void font_factory::UnrefFace(font_instance *who) { - if ( who == NULL ) return; - if ( loadedFaces.find(who->descr) == loadedFaces.end() ) { - // not found - char *tc = pango_font_description_to_string(who->descr); - g_warning("unrefFace %p=%s: failed\n",who,tc); - g_free(tc); - } else { - loadedFaces.erase(loadedFaces.find(who->descr)); - // printf("unrefFace %p: success\n",who); + if ( who ) { + FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr); + + if ( loadedFaces.find(who->descr) == loadedFaces.end() ) { + // not found + char *tc = pango_font_description_to_string(who->descr); + g_warning("unrefFace %p=%s: failed\n",who,tc); + g_free(tc); + } else { + loadedFaces.erase(loadedFaces.find(who->descr)); + // printf("unrefFace %p: success\n",who); + } } } diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h index 5253f6bbd..0118c862d 100644 --- a/src/libnrtype/FontFactory.h +++ b/src/libnrtype/FontFactory.h @@ -11,7 +11,6 @@ #include <functional> #include <algorithm> -#include <tr1/unordered_map> #ifdef HAVE_CONFIG_H # include <config.h> @@ -84,31 +83,29 @@ public: double fontSize; /**< The huge fontsize used as workaround for hinting. * Different between freetype and win32. */ - std::tr1::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> loadedFaces; - font_factory(); virtual ~font_factory(); /// Returns the default font_factory. static font_factory* Default(); - + /// Constructs a pango string for use with the fontStringMap (see below) Glib::ustring ConstructFontSpecification(PangoFontDescription *font); Glib::ustring ConstructFontSpecification(font_instance *font); - + /// Returns strings to be used in the UI for family and face (or "style" as the column is labeled) Glib::ustring GetUIFamilyString(PangoFontDescription const *fontDescr); Glib::ustring GetUIStyleString(PangoFontDescription const *fontDescr); - + /// Modifiers for the font specification (returns new font specification) Glib::ustring ReplaceFontSpecificationFamily(const Glib::ustring & fontSpec, const Glib::ustring & newFamily); Glib::ustring FontSpecificationSetItalic(const Glib::ustring & fontSpec, bool turnOn); Glib::ustring FontSpecificationSetBold(const Glib::ustring & fontSpec, bool turnOn); - + // Gathers all strings needed for UI while storing pango information in // fontInstanceMap and fontStringMap void GetUIFamiliesAndStyles(FamilyToStylesMap *map); - + /// Retrieve a font_instance from a style object, first trying to use the font-specification, the CSS information font_instance* FaceFromStyle(SPStyle const *style); @@ -129,17 +126,19 @@ public: // internal void AddInCache(font_instance *who); - + private: + void* loadedPtr; + // These two maps are used for translating between what's in the UI and a pango // font description. This is necessary because Pango cannot always // reproduce these structures from the names it gave us in the first place. - + // Key: A string produced by font_factory::ConstructFontSpecification // Value: The associated PangoFontDescription typedef std::map<Glib::ustring, PangoFontDescription *> PangoStringToDescrMap; PangoStringToDescrMap fontInstanceMap; - + // Key: Family name in UI + Style name in UI // Value: The associated string that should be produced with font_factory::ConstructFontSpecification typedef std::map<Glib::ustring, Glib::ustring> UIStringToPangoStringMap; diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp index d9a0c43e6..6b0725b34 100644 --- a/src/libnrtype/FontInstance.cpp +++ b/src/libnrtype/FontInstance.cpp @@ -29,6 +29,22 @@ # include FT_TRUETYPE_TABLES_H # include <pango/pangoft2.h> +#include <tr1/unordered_map> + + +// the various raster_font in use at a given time are held in a hash_map whose indices are the +// styles, hence the 2 following 'classes' +struct font_style_hash : public std::unary_function<font_style, size_t> { + size_t operator()(font_style const &x) const; +}; + +struct font_style_equal : public std::binary_function<font_style, font_style, bool> { + bool operator()(font_style const &a, font_style const &b) const; +}; + + +typedef std::tr1::unordered_map<font_style, raster_font*, font_style_hash, font_style_equal> StyleMap; + size_t font_style_hash::operator()(const font_style &x) const { @@ -155,36 +171,61 @@ static int ft2_cubic_to(FREETYPE_VECTOR *control1, FREETYPE_VECTOR *control2, FR * */ -font_instance::font_instance(void) +font_instance::font_instance(void) : + pFont(0), + descr(0), + refCount(0), + daddy(0), + nbGlyph(0), + maxGlyph(0), + glyphs(0), + loadedPtr(new StyleMap()), + theFace(0) { - //printf("font instance born\n"); - descr=NULL; - pFont=NULL; - refCount=0; - daddy=NULL; - nbGlyph=maxGlyph=0; - glyphs=NULL; - theFace=NULL; + //printf("font instance born\n"); } font_instance::~font_instance(void) { - if ( daddy ) daddy->UnrefFace(this); - //printf("font instance death\n"); - if ( pFont ) g_object_unref(pFont); - pFont=NULL; - if ( descr ) pango_font_description_free(descr); - descr=NULL; - // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch - theFace=NULL; - - for (int i=0;i<nbGlyph;i++) { - if ( glyphs[i].outline ) delete glyphs[i].outline; - if ( glyphs[i].pathvector ) delete glyphs[i].pathvector; - } - if ( glyphs ) free(glyphs); - nbGlyph=maxGlyph=0; - glyphs=NULL; + if ( loadedPtr ) { + StyleMap* tmp = static_cast<StyleMap*>(loadedPtr); + delete tmp; + loadedPtr = 0; + } + + if ( daddy ) { + daddy->UnrefFace(this); + daddy = 0; + } + + //printf("font instance death\n"); + if ( pFont ) { + g_object_unref(pFont); + pFont = 0; + } + + if ( descr ) { + pango_font_description_free(descr); + descr = 0; + } + + // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch + theFace = 0; + + for (int i=0;i<nbGlyph;i++) { + if ( glyphs[i].outline ) { + delete glyphs[i].outline; + } + if ( glyphs[i].pathvector ) { + delete glyphs[i].pathvector; + } + } + if ( glyphs ) { + free(glyphs); + glyphs = 0; + } + nbGlyph = 0; + maxGlyph = 0; } void font_instance::Ref(void) @@ -728,7 +769,8 @@ raster_font* font_instance::RasterFont(const font_style &inStyle) nStyle.dashes=(double*)malloc(nStyle.nbDash*sizeof(double)); memcpy(nStyle.dashes,savDashes,nStyle.nbDash*sizeof(double)); } - if ( loadedStyles.find(nStyle) == loadedStyles.end() ) { + StyleMap& loadedStyles = *static_cast<StyleMap*>(loadedPtr); + if ( loadedStyles.find(nStyle) == loadedStyles.end() ) { raster_font *nR = new raster_font(nStyle); nR->Ref(); nR->daddy=this; @@ -746,15 +788,17 @@ raster_font* font_instance::RasterFont(const font_style &inStyle) void font_instance::RemoveRasterFont(raster_font* who) { - if ( who == NULL ) return; - if ( loadedStyles.find(who->style) == loadedStyles.end() ) { - //g_print("RemoveRasterFont failed \n"); - // not found - } else { - loadedStyles.erase(loadedStyles.find(who->style)); - //g_print("RemoveRasterFont\n"); - Unref(); - } + if ( who ) { + StyleMap& loadedStyles = *static_cast<StyleMap*>(loadedPtr); + if ( loadedStyles.find(who->style) == loadedStyles.end() ) { + //g_print("RemoveRasterFont failed \n"); + // not found + } else { + loadedStyles.erase(loadedStyles.find(who->style)); + //g_print("RemoveRasterFont\n"); + Unref(); + } + } } diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 7a2924d98..f6b9688bb 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -1467,8 +1467,12 @@ bool Layout::Calculator::calculate() } para.free(); - if (_scanline_maker) + if (_scanline_maker) { delete _scanline_maker; + _flow._input_truncated = false; + } else { + _flow._input_truncated = true; + } return true; } diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp index 2ee0051a4..fb2769edc 100644 --- a/src/libnrtype/Layout-TNG-Input.cpp +++ b/src/libnrtype/Layout-TNG-Input.cpp @@ -16,6 +16,7 @@ #include "style.h" #include "svg/svg-length.h" #include "sp-object.h" +#include "sp-string.h" #include "FontFactory.h" namespace Inkscape { diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp index 0fc061bfc..0682e3570 100644 --- a/src/libnrtype/Layout-TNG-OutIter.cpp +++ b/src/libnrtype/Layout-TNG-OutIter.cpp @@ -221,6 +221,35 @@ Geom::Point Layout::characterAnchorPoint(iterator const &it) const } } +boost::optional<Geom::Point> Layout::baselineAnchorPoint() const +{ + iterator pos = this->begin(); + Geom::Point left_pt = this->characterAnchorPoint(pos); + pos.thisEndOfLine(); + Geom::Point right_pt = this->characterAnchorPoint(pos); + + if (this->_blockProgression() == LEFT_TO_RIGHT || this->_blockProgression() == RIGHT_TO_LEFT) { + left_pt = Geom::Point(left_pt[Geom::Y], left_pt[Geom::X]); + right_pt = Geom::Point(right_pt[Geom::Y], right_pt[Geom::X]); + } + + switch (this->paragraphAlignment(pos)) { + case LEFT: + case FULL: + return left_pt; + break; + case CENTER: + return (left_pt + right_pt)/2; // middle point + break; + case RIGHT: + return right_pt; + break; + default: + return boost::optional<Geom::Point>(); + break; + } +} + Geom::Point Layout::chunkAnchorPoint(iterator const &it) const { unsigned chunk_index; @@ -705,7 +734,7 @@ bool Layout::iterator::nextLineCursor(int n) unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line; if (line_index == _parent_layout->_lines.size() - 1) return false; // nowhere to go - else + else n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index)); if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) { // switching between shapes: adjust the stored x to compensate @@ -728,7 +757,7 @@ bool Layout::iterator::prevLineCursor(int n) line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line; if (line_index == 0) return false; // nowhere to go - else + else n = MIN (n, static_cast<int>(line_index)); if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) { // switching between shapes: adjust the stored x to compensate diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index 2b4b80e7c..d6b68ab40 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -513,8 +513,10 @@ void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path) _glyphs[glyph_index].y = midpoint[1] - _lines.front().baseline_y + tangent[1] * tangent_shift + tangent[0] * normal_shift; _glyphs[glyph_index].rotation += rotation; } + _input_truncated = false; } else { // outside the bounds of the path: hide the glyphs _characters[char_index].in_glyph = -1; + _input_truncated = true; } g_free(midpoint_otp); diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index 19680b140..0a2463a56 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -212,6 +212,10 @@ public: bool inputExists() const {return !_input_stream.empty();} + bool _input_truncated; + bool inputTruncated() const + {return _input_truncated;} + /** adds a new piece of text to the end of the current list of text to be processed. This method can only add text of a consistent style. To add lots of different styles, call it lots of times. @@ -480,6 +484,10 @@ public: /** For latin text, the left side of the character, on the baseline */ Geom::Point characterAnchorPoint(iterator const &it) const; + /** For left aligned text, the leftmost end of the baseline + For rightmost text, the rightmost... you probably got it by now ;-)*/ + boost::optional<Geom::Point> baselineAnchorPoint() const; + /** This is that value to apply to the x,y attributes of tspan role=line elements, and hence it takes alignment into account. */ Geom::Point chunkAnchorPoint(iterator const &it) const; diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index ce3f6cf1b..521c9a424 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -1,7 +1,6 @@ #ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H #define SEEN_LIBNRTYPE_FONT_INSTANCE_H -#include <tr1/unordered_map> #include <map> #include <pango/pango-types.h> #include <pango/pango-font.h> @@ -16,33 +15,21 @@ #include <2geom/d2.h> // the font_instance are the template of several raster_font; they provide metrics and outlines -// that are drawn by the raster_font, so the raster_font needs info relative to the way the +// that are drawn by the raster_font, so the raster_font needs info relative to the way the // font need to be drawn. note that fontsize is a scale factor in the transform matrix // of the style -// the various raster_font in use at a given time are held in a hash_map whose indices are the -// styles, hence the 2 following 'classes' -struct font_style_hash : public std::unary_function<font_style, size_t> { - size_t operator()(font_style const &x) const; -}; - -struct font_style_equal : public std::binary_function<font_style, font_style, bool> { - bool operator()(font_style const &a, font_style const &b) const; -}; - class font_instance { public: - // hashmap to get the raster_font for a given style - std::tr1::unordered_map<font_style, raster_font*, font_style_hash, font_style_equal> loadedStyles; - // the real source of the font + // the real source of the font PangoFont* pFont; - // depending on the rendering backend, different temporary data + // depending on the rendering backend, different temporary data - // that's the font's fingerprint; this particular PangoFontDescription gives the entry at which this font_instance - // resides in the font_factory loadedFaces hash_map + // that's the font's fingerprint; this particular PangoFontDescription gives the entry at which this font_instance + // resides in the font_factory loadedFaces hash_map PangoFontDescription* descr; - // refcount + // refcount int refCount; - // font_factory owning this font_instance + // font_factory owning this font_instance font_factory* daddy; // common glyph definitions for all the rasterfonts @@ -58,38 +45,38 @@ public: bool IsOutlineFont(void); // utility void InstallFace(PangoFont* iFace); // utility; should reset the pFont field if loading failed - // in case the PangoFont is a bitmap font, for example. that way, the calling function - // will be able to check the validity of the font before installing it in loadedFaces + // in case the PangoFont is a bitmap font, for example. that way, the calling function + // will be able to check the validity of the font before installing it in loadedFaces void InitTheFace(); int MapUnicodeChar(gunichar c); // calls the relevant unicode->glyph index function void LoadGlyph(int glyph_id); // the main backend-dependent function - // loads the given glyph's info - - // nota: all coordinates returned by these functions are on a [0..1] scale; you need to multiply - // by the fontsize to get the real sizes + // loads the given glyph's info + + // nota: all coordinates returned by these functions are on a [0..1] scale; you need to multiply + // by the fontsize to get the real sizes Path* Outline(int glyph_id, Path *copyInto=NULL); - // queries the outline of the glyph (in livarot Path form), and copies it into copyInto instead - // of allocating a new Path if copyInto != NULL + // queries the outline of the glyph (in livarot Path form), and copies it into copyInto instead + // of allocating a new Path if copyInto != NULL Geom::PathVector* PathVector(int glyph_id); // returns the 2geom-type pathvector for this glyph. no refcounting needed, it's deallocated when the font_instance dies double Advance(int glyph_id, bool vertical); - // nominal advance of the font. + // nominal advance of the font. bool FontMetrics(double &ascent, double &descent, double &leading); bool FontSlope(double &run, double &rise); // for generating slanted cursors for oblique fonts Geom::OptRect BBox(int glyph_id); - // creates a rasterfont for the given style + // creates a rasterfont for the given style raster_font* RasterFont(Geom::Matrix const &trs, double stroke_width, bool vertical = false, JoinType stroke_join = join_straight, ButtType stroke_cap = butt_straight, float miter_limit = 4.0); - // the dashes array in iStyle is copied + // the dashes array in iStyle is copied raster_font* RasterFont(font_style const &iStyle); - // private use: tells the font_instance that the raster_font 'who' has died + // private use: tells the font_instance that the raster_font 'who' has died void RemoveRasterFont(raster_font *who); - // attribute queries + // attribute queries unsigned Name(gchar *str, unsigned size); unsigned PSName(gchar *str, unsigned size); unsigned Family(gchar *str, unsigned size); @@ -98,10 +85,13 @@ public: private: void FreeTheFace(); + // hashmap to get the raster_font for a given style + void* loadedPtr; // Pointer to a hash_map. Moved into .cpp to not expose use of __gnu_cxx extension. + #ifdef USE_PANGO_WIN32 HFONT theFace; #else - FT_Face theFace; + FT_Face theFace; // it's a pointer in fact; no worries to ref/unref it, pango does its magic // as long as pFont is valid, theFace is too #endif diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp index 5d5a77280..696f92405 100644 --- a/src/line-snapper.cpp +++ b/src/line-snapper.cpp @@ -23,21 +23,17 @@ Inkscape::LineSnapper::LineSnapper(SnapManager *sm, Geom::Coord const d) : Snapp void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &/*f*/, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, std::vector<SPItem const *> const */*it*/, - std::vector<std::pair<Geom::Point, int> > */*unselected_nodes*/) const + std::vector<Inkscape::SnapCandidatePoint> */*unselected_nodes*/) const { if (!(_snap_enabled && _snapmanager->snapprefs.getSnapFrom(t)) ) { return; } /* Get the lines that we will try to snap to */ - const LineList lines = _getSnapLines(p); - - // std::cout << "snap point " << p << " to: " << std::endl; + const LineList lines = _getSnapLines(p.getPoint()); for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { Geom::Point const p1 = i->second; // point at guide/grid line @@ -45,17 +41,17 @@ void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, // std::cout << " line through " << i->second << " with normal " << i->first; g_assert(i->first != Geom::Point(0,0)); // we cannot project on an linesegment of zero length - Geom::Point const p_proj = Geom::projection(p, Geom::Line(p1, p2)); - Geom::Coord const dist = Geom::L2(p_proj - p); + Geom::Point const p_proj = Geom::projection(p.getPoint(), Geom::Line(p1, p2)); + Geom::Coord const dist = Geom::L2(p_proj - p.getPoint()); //Store any line that's within snapping range if (dist < getSnapperTolerance()) { - _addSnappedLine(sc, p_proj, dist, source_type, i->first, i->second); + _addSnappedLine(sc, p_proj, dist, p.getSourceType(), p.getSourceNum(), i->first, i->second); // For any line that's within range, we will also look at it's "point on line" p1. For guides // this point coincides with its origin; for grids this is of no use, but we cannot // discern between grids and guides here - Geom::Coord const dist_p1 = Geom::L2(p1 - p); + Geom::Coord const dist_p1 = Geom::L2(p1 - p.getPoint()); if (dist_p1 < getSnapperTolerance()) { - _addSnappedLinesOrigin(sc, p1, dist_p1, source_type); + _addSnappedLinesOrigin(sc, p1, dist_p1, p.getSourceType(), p.getSourceNum()); // Only relevant for guides; grids don't have an origin per line // Therefore _addSnappedLinesOrigin() will only be implemented for guides } @@ -67,9 +63,7 @@ void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &/*f*/, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &/*bbox_to_snap*/, ConstraintLine const &c, std::vector<SPItem const *> const */*it*/) const @@ -80,12 +74,12 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, } /* Get the lines that we will try to snap to */ - const LineList lines = _getSnapLines(p); + const LineList lines = _getSnapLines(p.getPoint()); for (LineList::const_iterator i = lines.begin(); i != lines.end(); i++) { if (Geom::L2(c.getDirection()) > 0) { // Can't do a constrained snap without a constraint // constraint line - Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; + Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p.getPoint(); Geom::Line line1(point_on_line, point_on_line + c.getDirection()); // grid/guide line @@ -106,19 +100,19 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, if (inters) { Geom::Point t = line1.pointAt((*inters).ta); - const Geom::Coord dist = Geom::L2(t - p); + const Geom::Coord dist = Geom::L2(t - p.getPoint()); if (dist < getSnapperTolerance()) { // When doing a constrained snap, we're already at an intersection. // This snappoint is therefore fully constrained, so there's no need // to look for additional intersections; just return the snapped point // and forget about the line - _addSnappedPoint(sc, t, dist, source_type); + _addSnappedPoint(sc, t, dist, p.getSourceType(), p.getSourceNum()); // For any line that's within range, we will also look at it's "point on line" p1. For guides // this point coincides with its origin; for grids this is of no use, but we cannot // discern between grids and guides here - Geom::Coord const dist_p1 = Geom::L2(p1 - p); + Geom::Coord const dist_p1 = Geom::L2(p1 - p.getPoint()); if (dist_p1 < getSnapperTolerance()) { - _addSnappedLinesOrigin(sc, p1, dist_p1, source_type); + _addSnappedLinesOrigin(sc, p1, dist_p1, p.getSourceType(), p.getSourceNum()); // Only relevant for guides; grids don't have an origin per line // Therefore _addSnappedLinesOrigin() will only be implemented for guides } @@ -130,7 +124,7 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, // Will only be overridden in the guide-snapper class, because grid lines don't have an origin; the // grid-snapper classes will use this default empty method -void Inkscape::LineSnapper::_addSnappedLinesOrigin(SnappedConstraints &/*sc*/, Geom::Point const /*origin*/, Geom::Coord const /*snapped_distance*/, SnapSourceType const &/*source_type*/) const +void Inkscape::LineSnapper::_addSnappedLinesOrigin(SnappedConstraints &/*sc*/, Geom::Point const /*origin*/, Geom::Coord const /*snapped_distance*/, SnapSourceType const &/*source_type*/, long /*source_num*/) const { } diff --git a/src/line-snapper.h b/src/line-snapper.h index 4ad08a99f..5845e081e 100644 --- a/src/line-snapper.h +++ b/src/line-snapper.h @@ -18,6 +18,7 @@ namespace Inkscape { +class SnapCandidatePoint; class LineSnapper : public Snapper { @@ -26,18 +27,14 @@ public: void freeSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, std::vector<SPItem const *> const *it, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const; + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes) const; void constrainedSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, ConstraintLine const &c, std::vector<SPItem const *> const *it) const; @@ -54,12 +51,12 @@ private: */ virtual LineList _getSnapLines(Geom::Point const &p) const = 0; - virtual void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, Geom::Point const normal_to_line, Geom::Point const point_on_line) const = 0; + virtual void _addSnappedLine(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const normal_to_line, Geom::Point const point_on_line) const = 0; // Will only be implemented for guide lines, because grid lines don't have an origin - virtual void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source) const; + virtual void _addSnappedLinesOrigin(SnappedConstraints &sc, Geom::Point const origin, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const; - virtual void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const = 0; + virtual void _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source, long source_num) const = 0; }; } diff --git a/src/live_effects/lpe-extrude.cpp b/src/live_effects/lpe-extrude.cpp index 93ab60fc5..af933eae6 100644 --- a/src/live_effects/lpe-extrude.cpp +++ b/src/live_effects/lpe-extrude.cpp @@ -73,6 +73,7 @@ LPEExtrude::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2 return pwd2_out; } + default: case 1: { Piecewise<D2<SBasis> > pwd2_out; bool closed_path = are_near(pwd2_in.firstValue(), pwd2_in.lastValue()); diff --git a/src/live_effects/lpe-perspective_path.cpp b/src/live_effects/lpe-perspective_path.cpp index 091a4d9ae..3d18318c5 100644 --- a/src/live_effects/lpe-perspective_path.cpp +++ b/src/live_effects/lpe-perspective_path.cpp @@ -58,7 +58,7 @@ LPEPerspectivePath::LPEPerspectivePath(LivePathEffectObject *lpeobject) : Persp3D *persp = persp3d_document_first_persp(inkscape_active_document()); - Proj::TransfMat3x4 pmat = persp->tmat; + Proj::TransfMat3x4 pmat = persp->perspective_impl->tmat; pmat.copy_tmat(tmat); } diff --git a/src/live_effects/lpe-recursiveskeleton.cpp b/src/live_effects/lpe-recursiveskeleton.cpp index 3cbac5829..50a3bfb6c 100644 --- a/src/live_effects/lpe-recursiveskeleton.cpp +++ b/src/live_effects/lpe-recursiveskeleton.cpp @@ -1,6 +1,6 @@ #define INKSCAPE_LPE_RECURSIVESKELETON_CPP /** \file - * @brief + * @brief * * Inspired by Hofstadter's 'Goedel Escher Bach', chapter V. */ @@ -52,7 +52,6 @@ LPERecursiveSkeleton::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > co std::vector<Piecewise<D2<SBasis> > > pre_output; double prop_scale = 1.0; - double fuse_tolerance = 0; D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pwd2_in); Piecewise<SBasis> x0 = false /*vertical_pattern.get_value()*/ ? Piecewise<SBasis>(patternd2[1]) : Piecewise<SBasis>(patternd2[0]); @@ -95,9 +94,10 @@ LPERecursiveSkeleton::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > co double scaling = 1; scaling = (uskeleton.domain().extent() - toffset)/pattBndsX->extent(); - + + // TODO investigate why pattWidth is not being used: double pattWidth = pattBndsX->extent() * scaling; - + if (scaling != 1.0) { x*=scaling; } diff --git a/src/live_effects/lpe-rough-hatches.cpp b/src/live_effects/lpe-rough-hatches.cpp index bcfd0f373..228857ebf 100644 --- a/src/live_effects/lpe-rough-hatches.cpp +++ b/src/live_effects/lpe-rough-hatches.cpp @@ -94,7 +94,7 @@ public: LevelsCrossings(std::vector<std::vector<double> > const ×, Piecewise<D2<SBasis> > const &f, Piecewise<SBasis> const &dx){ - + for (unsigned i=0; i<times.size(); i++){ LevelCrossings lcs; for (unsigned j=0; j<times[i].size(); j++){ @@ -158,7 +158,7 @@ public: } } //set indexes to point to the next point in the "snake walk" - //follow_level's meaning: + //follow_level's meaning: // 0=yes upward // 1=no, last move was upward, // 2=yes downward @@ -181,7 +181,7 @@ public: direction += 1; return; } - double t = (*this)[level][idx].t; + //double t = (*this)[level][idx].t; double sign = ((*this)[level][idx].sign ? 1 : -1); //---double next_t = t; //level += 1; @@ -288,13 +288,13 @@ LPERoughHatches::~LPERoughHatches() } -Geom::Piecewise<Geom::D2<Geom::SBasis> > +Geom::Piecewise<Geom::D2<Geom::SBasis> > LPERoughHatches::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in){ //std::cout<<"doEffect_pwd2:\n"; Piecewise<D2<SBasis> > result; - + Piecewise<D2<SBasis> > transformed_pwd2_in = pwd2_in; Point start = pwd2_in.segs.front().at0(); Point end = pwd2_in.segs.back().at1(); @@ -324,11 +324,11 @@ LPERoughHatches::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & Matrix mat(-hatches_dir[Y], hatches_dir[X], hatches_dir[X], hatches_dir[Y],0,0); transformed_pwd2_in = transformed_pwd2_in * mat; transformed_org *= mat; - + std::vector<std::vector<Point> > snakePoints; snakePoints = linearSnake(transformed_pwd2_in, transformed_org); if ( snakePoints.size() > 0 ){ - Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints); + Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints); smthSnake = smthSnake*mat.inverse(); if (do_bend.get_value()){ smthSnake = smthSnake*bend_mat; @@ -354,7 +354,7 @@ LPERoughHatches::generateLevels(Interval const &domain, double x_org){ while (x < domain.max()){ result.push_back(x); double rdm = 1; - if (dist_rdm.get_value() != 0) + if (dist_rdm.get_value() != 0) rdm = 1.+ double((2*dist_rdm - dist_rdm.get_value()))/100.; x+= step*rdm; step*=scale;//(1.+double(growth)); @@ -366,7 +366,7 @@ LPERoughHatches::generateLevels(Interval const &domain, double x_org){ //------------------------------------------------------- // Walk through the intersections to create linear hatches //------------------------------------------------------- -std::vector<std::vector<Point> > +std::vector<std::vector<Point> > LPERoughHatches::linearSnake(Piecewise<D2<SBasis> > const &f, Point const &org){ //std::cout<<"linearSnake:\n"; @@ -401,14 +401,14 @@ LPERoughHatches::linearSnake(Piecewise<D2<SBasis> > const &f, Point const &org){ unsigned i,j; lscs.findFirstUnused(i,j); - + std::vector<Point> result_component; int n = int((range->min()-org[X])/hatch_dist); - - while ( i < lscs.size() ){ + + while ( i < lscs.size() ){ int dir = 0; //switch orientation of first segment according to starting point. - if (i % 2 == n%2 && j < lscs[i].size()-1 && !lscs[i][j].used){ + if ((i % 2 == n % 2) && ((j + 1) < lscs[i].size()) && !lscs[i][j].used){ j += 1; dir = 2; } @@ -428,7 +428,7 @@ LPERoughHatches::linearSnake(Piecewise<D2<SBasis> > const &f, Point const &org){ //------------------------------------------------------- // Smooth the linear hatches according to params... //------------------------------------------------------- -Piecewise<D2<SBasis> > +Piecewise<D2<SBasis> > LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake){ Piecewise<D2<SBasis> > result; @@ -444,7 +444,7 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake Geom::Path res_comp_top(last_pt); Geom::Path res_comp_bot(last_pt); unsigned i=1; - //bool is_top = true;//Inversion here; due to downward y? + //bool is_top = true;//Inversion here; due to downward y? bool is_top = ( linearSnake[comp][0][Y] < linearSnake[comp][1][Y] ); while( i+1<linearSnake[comp].size() ){ @@ -454,18 +454,18 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake double scale_in = (is_top ? scale_tf : scale_bf ); double scale_out = (is_top ? scale_tb : scale_bb ); if (is_top){ - if (top_edge_variation.get_value() != 0) + if (top_edge_variation.get_value() != 0) new_pt[Y] += double(top_edge_variation)-top_edge_variation.get_value()/2.; - if (top_tgt_variation.get_value() != 0) + if (top_tgt_variation.get_value() != 0) new_pt[X] += double(top_tgt_variation)-top_tgt_variation.get_value()/2.; if (top_smth_variation.get_value() != 0) { scale_in*=(100.-double(top_smth_variation))/100.; scale_out*=(100.-double(top_smth_variation))/100.; } }else{ - if (bot_edge_variation.get_value() != 0) + if (bot_edge_variation.get_value() != 0) new_pt[Y] += double(bot_edge_variation)-bot_edge_variation.get_value()/2.; - if (bot_tgt_variation.get_value() != 0) + if (bot_tgt_variation.get_value() != 0) new_pt[X] += double(bot_tgt_variation)-bot_tgt_variation.get_value()/2.; if (bot_smth_variation.get_value() != 0) { scale_in*=(100.-double(bot_smth_variation))/100.; @@ -474,7 +474,7 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake } Point new_hdle_in = new_pt + (pt0-pt1) * (scale_in /2.); Point new_hdle_out = new_pt - (pt0-pt1) * (scale_out/2.); - + if ( fat_output.get_value() ){ //double scaled_width = double((is_top ? stroke_width_top : stroke_width_bot))/(pt1[X]-pt0[X]); double scaled_width = 1./(pt1[X]-pt0[X]); @@ -494,7 +494,7 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake //TODO: find a good way to handle limit cases (small smthness, large stroke). //if (inside_hdle_in[X] > inside[X]) inside_hdle_in = inside; //if (inside_hdle_out[X] < inside[X]) inside_hdle_out = inside; - + if (is_top){ res_comp_top.appendNew<CubicBezier>(last_top_hdle,new_hdle_in,new_pt); res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,inside_hdle_in,inside); @@ -509,7 +509,7 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake }else{ res_comp.appendNew<CubicBezier>(last_hdle,new_hdle_in,new_pt); } - + last_hdle = new_hdle_out; i+=2; is_top = !is_top; @@ -525,7 +525,7 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake if ( fat_output.get_value() ){ res_comp = res_comp_bot; res_comp.append(res_comp_top.reverse(),Geom::Path::STITCH_DISCONTINUOUS); - } + } result.concat(res_comp.toPwSb()); } } diff --git a/src/live_effects/lpe-ruler.cpp b/src/live_effects/lpe-ruler.cpp index 80970fd8a..35c9ea695 100644 --- a/src/live_effects/lpe-ruler.cpp +++ b/src/live_effects/lpe-ruler.cpp @@ -40,8 +40,8 @@ static const Util::EnumDataConverter<BorderMarkType> BorderMarkTypeConverter(Bor LPERuler::LPERuler(LivePathEffectObject *lpeobject) : Effect(lpeobject), - unit(_("Unit"), _("Unit"), "unit", &wr, this), mark_distance(_("Mark distance"), _("Distance between successive ruler marks"), "mark_distance", &wr, this, 20.0), + unit(_("Unit"), _("Unit"), "unit", &wr, this), mark_length(_("Major length"), _("Length of major ruler marks"), "mark_length", &wr, this, 14.0), minor_mark_length(_("Minor length"), _("Length of minor ruler marks"), "minor_mark_length", &wr, this, 7.0), major_mark_steps(_("Major steps"), _("Draw a major mark every ... steps"), "major_mark_steps", &wr, this, 5), diff --git a/src/live_effects/parameter/parameter.cpp b/src/live_effects/parameter/parameter.cpp index a8ea15744..57d583ba6 100644 --- a/src/live_effects/parameter/parameter.cpp +++ b/src/live_effects/parameter/parameter.cpp @@ -32,9 +32,9 @@ Parameter::Parameter( const Glib::ustring& label, const Glib::ustring& tip, param_wr(wr), param_label(label), oncanvas_editable(false), + widget_is_visible(true), param_tooltip(tip), - param_effect(effect), - widget_is_visible(true) + param_effect(effect) { } diff --git a/src/main.cpp b/src/main.cpp index f96d99e11..75e882e99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -566,8 +566,12 @@ main(int argc, char **argv) // TODO these should use xxxW() calls explicitly and convert UTF-16 <--> UTF-8 SetCurrentDirectory(homedir.c_str()); _win32_set_inkscape_env(homedir); - RegistryTool rt; - rt.setPathInfo(); + // Don't touch the registry (works fine without it) for Inkscape Portable + gchar const *val = g_getenv("INKSCAPE_PORTABLE_PROFILE_DIR"); + if (!val) { + RegistryTool rt; + rt.setPathInfo(); + } #endif // Prevents errors like "Unable to wrap GdkPixbuf..." (in nr-filter-image.cpp for example) diff --git a/src/marker-test.h b/src/marker-test.h new file mode 100644 index 000000000..5b84dcc66 --- /dev/null +++ b/src/marker-test.h @@ -0,0 +1,39 @@ +/** @file + * @brief Unit tests for SVG marker handling + */ +/* Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * + * This file is released into the public domain. + */ + +#include <cxxtest/TestSuite.h> + +#include "sp-marker-loc.h" + +class MarkerTest : public CxxTest::TestSuite +{ +public: + + void testMarkerLoc() + { + // code depends on these *exact* values, so check them here. + TS_ASSERT_EQUALS(SP_MARKER_LOC, 0); + TS_ASSERT_EQUALS(SP_MARKER_LOC_START, 1); + TS_ASSERT_EQUALS(SP_MARKER_LOC_MID, 2); + TS_ASSERT_EQUALS(SP_MARKER_LOC_END, 3); + TS_ASSERT_EQUALS(SP_MARKER_LOC_QTY, 4); + } + +}; + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h index d7a18f211..aeb6500bf 100644 --- a/src/menus-skeleton.h +++ b/src/menus-skeleton.h @@ -40,8 +40,7 @@ static char const menus_skeleton[] = " <verb verb-id=\"DialogMetadata\" />\n" " <verb verb-id=\"DialogPreferences\" />\n" " <verb verb-id=\"DialogInput\" />\n" -// TODO look at some dynamic option for changing the menu tree: -//" <verb verb-id=\"DialogInput2\" />\n" +" <verb verb-id=\"DialogInput2\" />\n" " <separator/>\n" " <verb verb-id=\"FileClose\" />\n" " <verb verb-id=\"FileQuit\" />\n" @@ -110,6 +109,8 @@ static char const menus_skeleton[] = " <verb verb-id=\"ViewModeNormal\" radio=\"yes\" default=\"yes\"/>\n" " <verb verb-id=\"ViewModeNoFilters\" radio=\"yes\"/>\n" " <verb verb-id=\"ViewModeOutline\" radio=\"yes\"/>\n" +" <verb verb-id=\"ViewModePrintColorsPreview\" radio=\"yes\"/>\n" +" <verb verb-id=\"DialogPrintColorsPreview\" />\n" " </submenu>\n" " <separator/>\n" " <verb verb-id=\"ToggleGrid\" />\n" diff --git a/src/nodepath.cpp b/src/nodepath.cpp index 36e4e0d8c..488642cb2 100644 --- a/src/nodepath.cpp +++ b/src/nodepath.cpp @@ -1367,13 +1367,13 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, * must provide that information. */ // Build a list of the unselected nodes to which the snapper should snap - std::vector<std::pair<Geom::Point, int> > unselected_nodes; + std::vector<Inkscape::SnapCandidatePoint> unselected_nodes; for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; if (!node->selected) { - unselected_nodes.push_back(std::make_pair(to_2geom(node->pos), node->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPTARGET_NODE_SMOOTH : Inkscape::SNAPTARGET_NODE_CUSP)); + unselected_nodes.push_back(Inkscape::SnapCandidatePoint(node->pos, Inkscape::SNAPSOURCE_UNDEFINED, node->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPTARGET_NODE_SMOOTH : Inkscape::SNAPTARGET_NODE_CUSP)); } } } @@ -1388,39 +1388,39 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *closest_node = NULL; Geom::Coord closest_dist = NR_HUGE; - if (closest_only) { - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin); - if (dist < closest_dist) { - closest_node = n; - closest_dist = dist; - } - } + if (closest_only) { + for (GList *l = nodepath->selected; l != NULL; l = l->next) { + Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; + Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin); + if (dist < closest_dist) { + closest_node = n; + closest_dist = dist; + } + } } - // Iterate through all selected nodes - m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes); - for (GList *l = nodepath->selected; l != NULL; l = l->next) { + // Iterate through all selected nodes + m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes); + for (GList *l = nodepath->selected; l != NULL; l = l->next) { Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one - Inkscape::SnappedPoint s; - Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP); - if (constrained) { - Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; - dedicated_constraint.setPoint(n->pos); - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint, false); - } else { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type); - } - - if (s.getSnapped()) { - s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin)); - if (!s.isOtherSnapBetter(best, true)) { - best = s; - best_pt = from_2geom(s.getPoint()) - n->pos; - } - } + Inkscape::SnappedPoint s; + Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP); + if (constrained) { + Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; + dedicated_constraint.setPoint(n->pos); + s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type), dedicated_constraint); + } else { + s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type)); + } + + if (s.getSnapped()) { + s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin)); + if (!s.isOtherSnapBetter(best, true)) { + best = s; + best_pt = from_2geom(s.getPoint()) - n->pos; + } + } } } @@ -3955,9 +3955,9 @@ static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, g Inkscape::SnappedPoint s; if ((state & GDK_SHIFT_MASK) != 0) { - // We will not try to snap when the shift-key is pressed - // so remove the old snap indicator and don't wait for it to time-out - desktop->snapindicator->remove_snaptarget(); + // We will not try to snap when the shift-key is pressed + // so remove the old snap indicator and don't wait for it to time-out + desktop->snapindicator->remove_snaptarget(); } Inkscape::NodePath::Node *othernode = opposite->other; @@ -3975,16 +3975,16 @@ static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, g p = n->pos + (scal / linelen) * ndelta; } if ((state & GDK_SHIFT_MASK) == 0) { - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta), false); + s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type), Inkscape::Snapper::ConstraintLine(p, ndelta)); } } else { if ((state & GDK_SHIFT_MASK) == 0) { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type); + s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type)); } } } else { if ((state & GDK_SHIFT_MASK) == 0) { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type); + s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type)); } } diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 22d438c1e..11685e25c 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -33,21 +33,12 @@ #include "helper/geom-curves.h" #include "desktop.h" -Inkscape::SnapCandidate::SnapCandidate(SPItem* item, bool clip_or_mask, Geom::Matrix additional_affine) - : item(item), clip_or_mask(clip_or_mask), additional_affine(additional_affine) -{ -} - -Inkscape::SnapCandidate::~SnapCandidate() -{ -} - Inkscape::ObjectSnapper::ObjectSnapper(SnapManager *sm, Geom::Coord const d) : Snapper(sm, d) { - _candidates = new std::vector<SnapCandidate>; - _points_to_snap_to = new std::vector<std::pair<Geom::Point, int> >; - _paths_to_snap_to = new std::vector<std::pair<Geom::PathVector*, SnapTargetType> >; + _candidates = new std::vector<SnapCandidateItem>; + _points_to_snap_to = new std::vector<Inkscape::SnapCandidatePoint>; + _paths_to_snap_to = new std::vector<Inkscape::SnapCandidatePath >; } Inkscape::ObjectSnapper::~ObjectSnapper() @@ -67,9 +58,9 @@ Inkscape::ObjectSnapper::~ObjectSnapper() */ Geom::Coord Inkscape::ObjectSnapper::getSnapperTolerance() const { - SPDesktop const *dt = _snapmanager->getDesktop(); - double const zoom = dt ? dt->current_zoom() : 1; - return _snapmanager->snapprefs.getObjectTolerance() / zoom; + SPDesktop const *dt = _snapmanager->getDesktop(); + double const zoom = dt ? dt->current_zoom() : 1; + return _snapmanager->snapprefs.getObjectTolerance() / zoom; } bool Inkscape::ObjectSnapper::getSnapperAlwaysSnap() const @@ -81,7 +72,6 @@ bool Inkscape::ObjectSnapper::getSnapperAlwaysSnap() const * Find all items within snapping range. * \param parent Pointer to the document's root, or to a clipped path or mask object * \param it List of items to ignore - * \param first_point If true then this point is the first one from a whole bunch of points * \param bbox_to_snap Bounding box hulling the whole bunch of points, all from the same selection and having the same transformation * \param DimensionToSnap Snap in X, Y, or both directions. */ @@ -159,7 +149,7 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, // See if the item is within range if (bbox_to_snap_incl.intersects(*bbox_of_item)) { // This item is within snapping range, so record it as a candidate - _candidates->push_back(SnapCandidate(item, clip_or_mask, additional_affine)); + _candidates->push_back(SnapCandidateItem(item, clip_or_mask, additional_affine)); // For debugging: print the id of the candidate to the console //SPObject *obj = (SPObject*)item; //std::cout << "Snap candidate added: " << obj->id << std::endl; @@ -173,7 +163,7 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapPreferences::PointType const &t, - bool const &first_point) const + bool const &first_point) const { // Now, let's first collect all points to snap to. If we have a whole bunch of points to snap, // e.g. when translating an item using the selector tool, then we will only do this for the @@ -186,10 +176,10 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapPreferences::PointType bool p_is_a_node = t & Inkscape::SnapPreferences::SNAPPOINT_NODE; bool p_is_a_bbox = t & Inkscape::SnapPreferences::SNAPPOINT_BBOX; - bool p_is_a_guide = t & Inkscape::SnapPreferences::SNAPPOINT_GUIDE; + bool p_is_other = t & Inkscape::SnapPreferences::SNAPPOINT_OTHER; // A point considered for snapping should be either a node, a bbox corner or a guide. Pick only ONE! - g_assert(!((p_is_a_node && p_is_a_bbox) || (p_is_a_bbox && p_is_a_guide) || (p_is_a_node && p_is_a_guide))); + g_assert(!((p_is_a_node && p_is_a_bbox) || (p_is_a_bbox && p_is_other) || (p_is_a_node && p_is_other))); if (_snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -203,7 +193,7 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapPreferences::PointType _getBorderNodes(_points_to_snap_to); } - for (std::vector<SnapCandidate>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { + for (std::vector<SnapCandidateItem>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { //Geom::Matrix i2doc(Geom::identity()); SPItem *root_item = (*i).item; if (SP_IS_USE((*i).item)) { @@ -212,61 +202,59 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapPreferences::PointType g_return_if_fail(root_item); //Collect all nodes so we can snap to them - if (p_is_a_node || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node) || p_is_a_guide) { - // Note: there are two ways in which intersections are considered: - // Method 1: Intersections are calculated for each shape individually, for both the - // snap source and snap target (see sp_shape_snappoints) - // Method 2: Intersections are calculated for each curve or line that we've snapped to, i.e. only for - // the target (see the intersect() method in the SnappedCurve and SnappedLine classes) - // Some differences: - // - Method 1 doesn't find intersections within a set of multiple objects - // - Method 2 only works for targets - // When considering intersections as snap targets: - // - Method 1 only works when snapping to nodes, whereas - // - Method 2 only works when snapping to paths - // - There will be performance differences too! - // If both methods are being used simultaneously, then this might lead to duplicate targets! - - // Well, here we will be looking for snap TARGETS. Both methods can therefore be used. - // When snapping to paths, we will get a collection of snapped lines and snapped curves. findBestSnap() will - // go hunting for intersections (but only when asked to in the prefs of course). In that case we can just - // temporarily block the intersections in sp_item_snappoints, we don't need duplicates. If we're not snapping to - // paths though but only to item nodes then we should still look for the intersections in sp_item_snappoints() - bool old_pref = _snapmanager->snapprefs.getSnapIntersectionCS(); - if (_snapmanager->snapprefs.getSnapToItemPath()) { - _snapmanager->snapprefs.setSnapIntersectionCS(false); - } - - sp_item_snappoints(root_item, true, *_points_to_snap_to, &_snapmanager->snapprefs); - - if (_snapmanager->snapprefs.getSnapToItemPath()) { - _snapmanager->snapprefs.setSnapIntersectionCS(old_pref); - } - } + if (p_is_a_node || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node) || p_is_other) { + // Note: there are two ways in which intersections are considered: + // Method 1: Intersections are calculated for each shape individually, for both the + // snap source and snap target (see sp_shape_snappoints) + // Method 2: Intersections are calculated for each curve or line that we've snapped to, i.e. only for + // the target (see the intersect() method in the SnappedCurve and SnappedLine classes) + // Some differences: + // - Method 1 doesn't find intersections within a set of multiple objects + // - Method 2 only works for targets + // When considering intersections as snap targets: + // - Method 1 only works when snapping to nodes, whereas + // - Method 2 only works when snapping to paths + // - There will be performance differences too! + // If both methods are being used simultaneously, then this might lead to duplicate targets! + + // Well, here we will be looking for snap TARGETS. Both methods can therefore be used. + // When snapping to paths, we will get a collection of snapped lines and snapped curves. findBestSnap() will + // go hunting for intersections (but only when asked to in the prefs of course). In that case we can just + // temporarily block the intersections in sp_item_snappoints, we don't need duplicates. If we're not snapping to + // paths though but only to item nodes then we should still look for the intersections in sp_item_snappoints() + bool old_pref = _snapmanager->snapprefs.getSnapIntersectionCS(); + if (_snapmanager->snapprefs.getSnapToItemPath()) { + _snapmanager->snapprefs.setSnapIntersectionCS(false); + } + + sp_item_snappoints(root_item, *_points_to_snap_to, &_snapmanager->snapprefs); + + if (_snapmanager->snapprefs.getSnapToItemPath()) { + _snapmanager->snapprefs.setSnapIntersectionCS(old_pref); + } + } //Collect the bounding box's corners so we can snap to them - if (p_is_a_bbox || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_bbox) || p_is_a_guide) { - // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox - // of the item AND the bbox of the clipping path at the same time - if (!(*i).clip_or_mask) { - Geom::OptRect b = sp_item_bbox_desktop(root_item, bbox_type); - getBBoxPoints(b, _points_to_snap_to, true, _snapmanager->snapprefs.getSnapToBBoxNode(), _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints(), _snapmanager->snapprefs.getSnapBBoxMidpoints()); - } - } + if (p_is_a_bbox || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_bbox) || p_is_other) { + // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox + // of the item AND the bbox of the clipping path at the same time + if (!(*i).clip_or_mask) { + Geom::OptRect b = sp_item_bbox_desktop(root_item, bbox_type); + getBBoxPoints(b, _points_to_snap_to, true, _snapmanager->snapprefs.getSnapToBBoxNode(), _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints(), _snapmanager->snapprefs.getSnapBBoxMidpoints()); + } + } } } } void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const + Inkscape::SnapCandidatePoint const &p, + std::vector<SnapCandidatePoint> *unselected_nodes) const { // Iterate through all nodes, find out which one is the closest to p, and snap to it! - _collectNodes(t, first_point); + _collectNodes(t, p.getSourceNum() == 0); if (unselected_nodes != NULL) { _points_to_snap_to->insert(_points_to_snap_to->end(), unselected_nodes->begin(), unselected_nodes->end()); @@ -275,10 +263,10 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, SnappedPoint s; bool success = false; - for (std::vector<std::pair<Geom::Point, int> >::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { - Geom::Coord dist = Geom::L2((*k).first - p); + for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { + Geom::Coord dist = Geom::L2((*k).getPoint() - p.getPoint()); if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) { - s = SnappedPoint((*k).first, source_type, static_cast<Inkscape::SnapTargetType>((*k).second), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + s = SnappedPoint((*k).getPoint(), p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); success = true; } } @@ -298,22 +286,22 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &s // Although we won't snap to paths here (which would give us under constrained snaps) we can still snap to intersections of paths. if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) { - _collectPaths(t, true); - _snapPaths(sc, t, p, SNAPSOURCE_GUIDE, true, NULL, NULL); - // The paths themselves should be discarded in findBestSnap(), as we should only snap to their intersections - } + _collectPaths(t, true); + _snapPaths(sc, t, Inkscape::SnapCandidatePoint(p, SNAPSOURCE_GUIDE), NULL, NULL); + // The paths themselves should be discarded in findBestSnap(), as we should only snap to their intersections + } SnappedPoint s; Geom::Coord tol = getSnapperTolerance(); - for (std::vector<std::pair<Geom::Point, int> >::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { + for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { // Project each node (*k) on the guide line (running through point p) - Geom::Point p_proj = Geom::projection((*k).first, Geom::Line(p, p + Geom::rot90(guide_normal))); - Geom::Coord dist = Geom::L2((*k).first - p_proj); // distance from node to the guide + Geom::Point p_proj = Geom::projection((*k).getPoint(), Geom::Line(p, p + Geom::rot90(guide_normal))); + Geom::Coord dist = Geom::L2((*k).getPoint() - p_proj); // distance from node to the guide Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location if ((dist < tol && dist2 < tol) || getSnapperAlwaysSnap()) { - s = SnappedPoint((*k).first, SNAPSOURCE_GUIDE, static_cast<Inkscape::SnapTargetType>((*k).second), dist, tol, getSnapperAlwaysSnap(), true); + s = SnappedPoint((*k).getPoint(), SNAPSOURCE_GUIDE, 0, (*k).getTargetType(), dist, tol, getSnapperAlwaysSnap(), true); sc.points.push_back(s); } } @@ -337,7 +325,7 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX; bool p_is_a_node = t & Inkscape::SnapPreferences::SNAPPOINT_NODE; - bool p_is_a_guide = t & Inkscape::SnapPreferences::SNAPPOINT_GUIDE; + bool p_is_other = t & Inkscape::SnapPreferences::SNAPPOINT_OTHER; if (_snapmanager->snapprefs.getSnapToBBoxPath()) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -350,11 +338,11 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType if (_snapmanager->snapprefs.getSnapToPageBorder()) { Geom::PathVector *border_path = _getBorderPathv(); if (border_path != NULL) { - _paths_to_snap_to->push_back(std::make_pair(border_path, SNAPTARGET_PAGE_BORDER)); + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(border_path, SNAPTARGET_PAGE_BORDER)); } } - for (std::vector<SnapCandidate>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { + for (std::vector<SnapCandidateItem>::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { /* Transform the requested snap point to this item's coordinates */ Geom::Matrix i2doc(Geom::identity()); @@ -373,7 +361,7 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType //Add the item's path to snap to if (_snapmanager->snapprefs.getSnapToItemPath()) { - if (p_is_a_guide || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node)) { + if (p_is_other || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node)) { // Snapping to the path of characters is very cool, but for a large // chunk of text this will take ages! So limit snapping to text paths // containing max. 240 characters. Snapping the bbox will not be affected @@ -398,7 +386,7 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType if (curve) { // We will get our own copy of the path, which must be freed at some point Geom::PathVector *borderpathv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine); - _paths_to_snap_to->push_back(std::make_pair(borderpathv, SNAPTARGET_PATH)); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(borderpathv, SNAPTARGET_PATH)); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. curve->unref(); } } @@ -407,7 +395,7 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType //Add the item's bounding box to snap to if (_snapmanager->snapprefs.getSnapToBBoxPath()) { - if (p_is_a_guide || !(_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) { + if (p_is_other || !(_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) { // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox // of the item AND the bbox of the clipping path at the same time if (!(*i).clip_or_mask) { @@ -415,7 +403,7 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType sp_item_invoke_bbox(root_item, rect, i2doc, TRUE, bbox_type); if (rect) { Geom::PathVector *path = _getPathvFromRect(*rect); - _paths_to_snap_to->push_back(std::make_pair(path, SNAPTARGET_BBOX_EDGE)); + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(path, SNAPTARGET_BBOX_EDGE)); } } } @@ -426,47 +414,40 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::SnapPreferences::PointType void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes, + Inkscape::SnapCandidatePoint const &p, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, SPPath const *selected_path) const { - _collectPaths(t, first_point); + _collectPaths(t, p.getSourceNum() == 0); // Now we can finally do the real snapping, using the paths collected above g_assert(_snapmanager->getDesktop() != NULL); - Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p); + Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p.getPoint()); bool const node_tool_active = _snapmanager->snapprefs.getSnapToItemPath() && selected_path != NULL; - if (first_point) { + if (p.getSourceNum() == 0) { /* findCandidates() is used for snapping to both paths and nodes. It ignores the path that is * currently being edited, because that path requires special care: when snapping to nodes * only the unselected nodes of that path should be considered, and these will be passed on separately. * This path must not be ignored however when snapping to the paths, so we add it here * manually when applicable. - * - * Note that this path must be the last in line! * */ if (node_tool_active) { SPCurve *curve = curve_for_item(SP_ITEM(selected_path)); if (curve) { Geom::PathVector *pathv = pathvector_for_curve(SP_ITEM(selected_path), curve, true, true, Geom::identity(), Geom::identity()); // We will get our own copy of the path, which must be freed at some point - _paths_to_snap_to->push_back(std::make_pair(pathv, SNAPTARGET_PATH)); + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(pathv, SNAPTARGET_PATH, true)); curve->unref(); } } } - for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) { - bool const being_edited = (node_tool_active && (*it_p) == _paths_to_snap_to->back()); + for (std::vector<Inkscape::SnapCandidatePath >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) { + bool const being_edited = node_tool_active && (*it_p).currently_being_edited; //if true then this pathvector it_pv is currently being edited in the node tool - // char * svgd = sp_svg_write_path(**it_p->first); - // std::cout << "Dumping the pathvector: " << svgd << std::endl; - - for(Geom::PathVector::iterator it_pv = (it_p->first)->begin(); it_pv != (it_p->first)->end(); ++it_pv) { + for(Geom::PathVector::iterator it_pv = (it_p->path_vector)->begin(); it_pv != (it_p->path_vector)->end(); ++it_pv) { // Find a nearest point for each curve within this path // n curves will return n time values with 0 <= t <= 1 std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc); @@ -503,7 +484,7 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, if (!being_edited || (c1 && c2)) { Geom::Coord const dist = Geom::distance(sp_doc, p_doc); if (dist < getSnapperTolerance()) { - sc.curves.push_back(Inkscape::SnappedCurve(sp_dt, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, source_type, it_p->second)); + sc.curves.push_back(Inkscape::SnappedCurve(sp_dt, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, p.getSourceType(), p.getSourceNum(), it_p->target_type)); } } } @@ -512,7 +493,7 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, } /* Returns true if point is coincident with one of the unselected nodes */ -bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::vector<std::pair<Geom::Point, int> > const *unselected_nodes) const +bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::vector<Inkscape::SnapCandidatePoint> const *unselected_nodes) const { if (unselected_nodes == NULL) { return false; @@ -522,8 +503,8 @@ bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::ve return false; } - for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = unselected_nodes->begin(); i != unselected_nodes->end(); i++) { - if (Geom::L2(point - (*i).first) < 1e-4) { + for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = unselected_nodes->begin(); i != unselected_nodes->end(); i++) { + if (Geom::L2(point - (*i).getPoint()) < 1e-4) { return true; } } @@ -533,18 +514,16 @@ bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::ve void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, ConstraintLine const &c) const { - _collectPaths(t, first_point); + _collectPaths(t, p.getSourceNum() == 0); // Now we can finally do the real snapping, using the paths collected above g_assert(_snapmanager->getDesktop() != NULL); - Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p); + Geom::Point const p_doc = _snapmanager->getDesktop()->dt2doc(p.getPoint()); Geom::Point direction_vector = c.getDirection(); if (!is_zero(direction_vector)) { @@ -554,7 +533,7 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, // The intersection point of the constraint line with any path, // must lie within two points on the constraintline: p_min_on_cl and p_max_on_cl // The distance between those points is twice the snapping tolerance - Geom::Point const p_proj_on_cl = p; // projection has already been taken care of in constrainedSnap in the snapmanager; + Geom::Point const p_proj_on_cl = p.getPoint(); // projection has already been taken care of in constrainedSnap in the snapmanager; Geom::Point const p_min_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl - getSnapperTolerance() * direction_vector); Geom::Point const p_max_on_cl = _snapmanager->getDesktop()->dt2doc(p_proj_on_cl + getSnapperTolerance() * direction_vector); @@ -564,9 +543,9 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, cl.appendNew<Geom::LineSegment>(p_max_on_cl); clv.push_back(cl); - for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { - if (k->first) { - Geom::CrossingSet cs = Geom::crossings(clv, *(k->first)); + for (std::vector<Inkscape::SnapCandidatePath >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { + if (k->path_vector) { + Geom::CrossingSet cs = Geom::crossings(clv, *(k->path_vector)); if (cs.size() > 0) { // We need only the first element of cs, because cl is only a single straight linesegment // This first element contains a vector filled with crossings of cl with k->first @@ -577,7 +556,7 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, // When it's within snapping range, then return it // (within snapping range == between p_min_on_cl and p_max_on_cl == 0 < ta < 1) Geom::Coord dist = Geom::L2(_snapmanager->getDesktop()->dt2doc(p_proj_on_cl) - p_inters); - SnappedPoint s(_snapmanager->getDesktop()->doc2dt(p_inters), source_type, k->second, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); + SnappedPoint s(_snapmanager->getDesktop()->doc2dt(p_inters), p.getSourceType(), p.getSourceNum(), k->target_type, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), true); sc.points.push_back(s); } } @@ -589,29 +568,27 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, std::vector<SPItem const *> const *it, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const + std::vector<SnapCandidatePoint> *unselected_nodes) const { if (_snap_enabled == false || _snapmanager->snapprefs.getSnapFrom(t) == false ) { return; } /* Get a list of all the SPItems that we will try to snap to */ - if (first_point) { - Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p, p); - _findCandidates(sp_document_root(_snapmanager->getDocument()), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity()); + if (p.getSourceNum() == 0) { + Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint()); + _findCandidates(sp_document_root(_snapmanager->getDocument()), it, p.getSourceNum() == 0, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity()); } if (_snapmanager->snapprefs.getSnapToItemNode() || _snapmanager->snapprefs.getSnapSmoothNodes() - || _snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapToPageBorder() - || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints() - || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints() - || _snapmanager->snapprefs.getIncludeItemCenter()) { - _snapNodes(sc, t, p, source_type, first_point, unselected_nodes); + || _snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapToPageBorder() + || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints() + || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints() + || _snapmanager->snapprefs.getIncludeItemCenter()) { + _snapNodes(sc, t, p, unselected_nodes); } if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) { @@ -625,22 +602,20 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, SPPath *path = NULL; if (it != NULL) { if (it->size() == 1 && SP_IS_PATH(*it->begin())) { - path = SP_PATH(*it->begin()); + path = SP_PATH(*it->begin()); } // else: *it->begin() might be a SPGroup, e.g. when editing a LPE of text that has been converted to a group of paths // as reported in bug #356743. In that case we can just ignore it, i.e. not snap to this item } - _snapPaths(sc, t, p, source_type, first_point, unselected_nodes, path); + _snapPaths(sc, t, p, unselected_nodes, path); } else { - _snapPaths(sc, t, p, source_type, first_point, NULL, NULL); + _snapPaths(sc, t, p, NULL, NULL); } } } void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, ConstraintLine const &c, std::vector<SPItem const *> const *it) const @@ -650,9 +625,9 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, } /* Get a list of all the SPItems that we will try to snap to */ - if (first_point) { - Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p, p); - _findCandidates(sp_document_root(_snapmanager->getDocument()), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity()); + if (p.getSourceNum() == 0) { + Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint()); + _findCandidates(sp_document_root(_snapmanager->getDocument()), it, p.getSourceNum() == 0, local_bbox_to_snap, TRANSL_SNAP_XY, false, Geom::identity()); } // A constrained snap, is a snap in only one degree of freedom (specified by the constraint line). @@ -665,7 +640,7 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, // so we will more or less snap to them anyhow. if (_snapmanager->snapprefs.getSnapToItemPath() || _snapmanager->snapprefs.getSnapToBBoxPath() || _snapmanager->snapprefs.getSnapToPageBorder()) { - _snapPathsConstrained(sc, t, p, source_type, first_point, c); + _snapPathsConstrained(sc, t, p, c); } } @@ -689,7 +664,7 @@ void Inkscape::ObjectSnapper::guideFreeSnap(SnappedConstraints &sc, } _findCandidates(sp_document_root(_snapmanager->getDocument()), &it, true, Geom::Rect(p, p), snap_dim, false, Geom::identity()); - _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, guide_normal); + _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_OTHER, p, guide_normal); } @@ -713,7 +688,7 @@ void Inkscape::ObjectSnapper::guideConstrainedSnap(SnappedConstraints &sc, } _findCandidates(sp_document_root(_snapmanager->getDocument()), &it, true, Geom::Rect(p, p), snap_dim, false, Geom::identity()); - _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, guide_normal); + _snapTranslatingGuideToNodes(sc, Inkscape::SnapPreferences::SNAPPOINT_OTHER, p, guide_normal); } @@ -723,13 +698,13 @@ void Inkscape::ObjectSnapper::guideConstrainedSnap(SnappedConstraints &sc, bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const { bool snap_to_something = _snapmanager->snapprefs.getSnapToItemPath() - || _snapmanager->snapprefs.getSnapToItemNode() - || _snapmanager->snapprefs.getSnapToBBoxPath() - || _snapmanager->snapprefs.getSnapToBBoxNode() - || _snapmanager->snapprefs.getSnapToPageBorder() - || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints() - || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints() - || _snapmanager->snapprefs.getIncludeItemCenter(); + || _snapmanager->snapprefs.getSnapToItemNode() + || _snapmanager->snapprefs.getSnapToBBoxPath() + || _snapmanager->snapprefs.getSnapToBBoxNode() + || _snapmanager->snapprefs.getSnapToPageBorder() + || _snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints() + || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints() + || _snapmanager->snapprefs.getIncludeItemCenter(); return (_snap_enabled && _snapmanager->snapprefs.getSnapModeBBoxOrNodes() && snap_to_something); } @@ -737,20 +712,20 @@ bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const bool Inkscape::ObjectSnapper::GuidesMightSnap() const // almost the same as ThisSnapperMightSnap above, but only looking at points (and not paths) { bool snap_to_something = _snapmanager->snapprefs.getSnapToItemNode() - || _snapmanager->snapprefs.getSnapToPageBorder() - || (_snapmanager->snapprefs.getSnapModeBBox() && _snapmanager->snapprefs.getSnapToBBoxNode()) - || (_snapmanager->snapprefs.getSnapModeBBox() && (_snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints())) - || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints())) - || (_snapmanager->snapprefs.getSnapModeNode() && _snapmanager->snapprefs.getIncludeItemCenter()) - || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapToItemPath() && _snapmanager->snapprefs.getSnapIntersectionCS())); + || _snapmanager->snapprefs.getSnapToPageBorder() + || (_snapmanager->snapprefs.getSnapModeBBox() && _snapmanager->snapprefs.getSnapToBBoxNode()) + || (_snapmanager->snapprefs.getSnapModeBBox() && (_snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints())) + || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapLineMidpoints() || _snapmanager->snapprefs.getSnapObjectMidpoints())) + || (_snapmanager->snapprefs.getSnapModeNode() && _snapmanager->snapprefs.getIncludeItemCenter()) + || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapToItemPath() && _snapmanager->snapprefs.getSnapIntersectionCS())); return (_snap_enabled && _snapmanager->snapprefs.getSnapModeGuide() && snap_to_something); } void Inkscape::ObjectSnapper::_clear_paths() const { - for (std::vector<std::pair<Geom::PathVector*, SnapTargetType> >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { - g_free(k->first); + for (std::vector<Inkscape::SnapCandidatePath >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { + g_free(k->path_vector); } _paths_to_snap_to->clear(); } @@ -772,33 +747,33 @@ Geom::PathVector* Inkscape::ObjectSnapper::_getPathvFromRect(Geom::Rect const re } } -void Inkscape::ObjectSnapper::_getBorderNodes(std::vector<std::pair<Geom::Point, int> > *points) const +void Inkscape::ObjectSnapper::_getBorderNodes(std::vector<SnapCandidatePoint> *points) const { Geom::Coord w = sp_document_width(_snapmanager->getDocument()); Geom::Coord h = sp_document_height(_snapmanager->getDocument()); - points->push_back(std::make_pair(Geom::Point(0,0), SNAPTARGET_PAGE_CORNER)); - points->push_back(std::make_pair(Geom::Point(0,h), SNAPTARGET_PAGE_CORNER)); - points->push_back(std::make_pair(Geom::Point(w,h), SNAPTARGET_PAGE_CORNER)); - points->push_back(std::make_pair(Geom::Point(w,0), SNAPTARGET_PAGE_CORNER)); + points->push_back(Inkscape::SnapCandidatePoint(Geom::Point(0,0), SNAPSOURCE_UNDEFINED, SNAPTARGET_PAGE_CORNER)); + points->push_back(Inkscape::SnapCandidatePoint(Geom::Point(0,h), SNAPSOURCE_UNDEFINED, SNAPTARGET_PAGE_CORNER)); + points->push_back(Inkscape::SnapCandidatePoint(Geom::Point(w,h), SNAPSOURCE_UNDEFINED, SNAPTARGET_PAGE_CORNER)); + points->push_back(Inkscape::SnapCandidatePoint(Geom::Point(w,0), SNAPSOURCE_UNDEFINED, SNAPTARGET_PAGE_CORNER)); } -void Inkscape::getBBoxPoints(Geom::OptRect const bbox, std::vector<std::pair<Geom::Point, int> > *points, bool const isTarget, bool const includeCorners, bool const includeLineMidpoints, bool const includeObjectMidpoints) +void Inkscape::getBBoxPoints(Geom::OptRect const bbox, std::vector<SnapCandidatePoint> *points, bool const isTarget, bool const includeCorners, bool const includeLineMidpoints, bool const includeObjectMidpoints) { - if (bbox) { - // collect the corners of the bounding box - for ( unsigned k = 0 ; k < 4 ; k++ ) { - if (includeCorners) { - points->push_back(std::make_pair((bbox->corner(k)), isTarget ? int(Inkscape::SNAPTARGET_BBOX_CORNER) : int(Inkscape::SNAPSOURCE_BBOX_CORNER))); - } - // optionally, collect the midpoints of the bounding box's edges too - if (includeLineMidpoints) { - points->push_back(std::make_pair((bbox->corner(k) + bbox->corner((k+1) % 4))/2, isTarget ? int(Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT) : int(Inkscape::SNAPSOURCE_BBOX_EDGE_MIDPOINT))); - } - } - if (includeObjectMidpoints) { - points->push_back(std::make_pair(bbox->midpoint(), isTarget ? int(Inkscape::SNAPTARGET_BBOX_MIDPOINT) : int(Inkscape::SNAPSOURCE_BBOX_MIDPOINT))); - } - } + if (bbox) { + // collect the corners of the bounding box + for ( unsigned k = 0 ; k < 4 ; k++ ) { + if (includeCorners) { + points->push_back(Inkscape::SnapCandidatePoint(bbox->corner(k), Inkscape::SNAPSOURCE_BBOX_CORNER, 0, Inkscape::SNAPTARGET_BBOX_CORNER, *bbox)); + } + // optionally, collect the midpoints of the bounding box's edges too + if (includeLineMidpoints) { + points->push_back(Inkscape::SnapCandidatePoint((bbox->corner(k) + bbox->corner((k+1) % 4))/2, Inkscape::SNAPSOURCE_BBOX_EDGE_MIDPOINT, 0, Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT, *bbox)); + } + } + if (includeObjectMidpoints) { + points->push_back(Inkscape::SnapCandidatePoint(bbox->midpoint(), Inkscape::SNAPSOURCE_BBOX_MIDPOINT, 0, Inkscape::SNAPTARGET_BBOX_MIDPOINT, *bbox)); + } + } } /* diff --git a/src/object-snapper.h b/src/object-snapper.h index baa60a096..556ff86de 100644 --- a/src/object-snapper.h +++ b/src/object-snapper.h @@ -17,6 +17,7 @@ #include "snapper.h" #include "sp-path.h" #include "splivarot.h" +#include "snap-candidate.h" struct SPNamedView; struct SPItem; @@ -25,74 +26,53 @@ struct SPObject; namespace Inkscape { -class SnapCandidate - -{ -public: - SnapCandidate(SPItem* item, bool clip_or_mask, Geom::Matrix _additional_affine); - ~SnapCandidate(); - - SPItem* item; // An item that is to be considered for snapping to - bool clip_or_mask; // If true, then item refers to a clipping path or a mask - - /* To find out the absolute position of a clipping path or mask, we not only need to know - * the transformation of the clipping path or mask itself, but also the transformation of - * the object to which the clip or mask is being applied; that transformation is stored here - */ - Geom::Matrix additional_affine; -}; - class ObjectSnapper : public Snapper { public: - ObjectSnapper(SnapManager *sm, Geom::Coord const d); + ObjectSnapper(SnapManager *sm, Geom::Coord const d); ~ObjectSnapper(); - enum DimensionToSnap { - GUIDE_TRANSL_SNAP_X, // For snapping a vertical guide (normal in the X-direction) to objects, - GUIDE_TRANSL_SNAP_Y, // For snapping a horizontal guide (normal in the Y-direction) to objects - ANGLED_GUIDE_TRANSL_SNAP, // For snapping an angled guide, while translating it accross the desktop - TRANSL_SNAP_XY}; // All other cases; for snapping to objects, other than guides - - void guideFreeSnap(SnappedConstraints &sc, - Geom::Point const &p, - Geom::Point const &guide_normal) const; - - void guideConstrainedSnap(SnappedConstraints &sc, - Geom::Point const &p, - Geom::Point const &guide_normal, - ConstraintLine const &c) const; - - bool ThisSnapperMightSnap() const; - bool GuidesMightSnap() const; - - Geom::Coord getSnapperTolerance() const; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom) - bool getSnapperAlwaysSnap() const; //if true, then the snapper will always snap, regardless of its tolerance - - void freeSnap(SnappedConstraints &sc, - Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, - Geom::OptRect const &bbox_to_snap, - std::vector<SPItem const *> const *it, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const; - - void constrainedSnap(SnappedConstraints &sc, - Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, - SnapSourceType const &source_type, - bool const &first_point, - Geom::OptRect const &bbox_to_snap, - ConstraintLine const &c, - std::vector<SPItem const *> const *it) const; + enum DimensionToSnap { + GUIDE_TRANSL_SNAP_X, // For snapping a vertical guide (normal in the X-direction) to objects, + GUIDE_TRANSL_SNAP_Y, // For snapping a horizontal guide (normal in the Y-direction) to objects + ANGLED_GUIDE_TRANSL_SNAP, // For snapping an angled guide, while translating it accross the desktop + TRANSL_SNAP_XY}; // All other cases; for snapping to objects, other than guides + + void guideFreeSnap(SnappedConstraints &sc, + Geom::Point const &p, + Geom::Point const &guide_normal) const; + + void guideConstrainedSnap(SnappedConstraints &sc, + Geom::Point const &p, + Geom::Point const &guide_normal, + ConstraintLine const &c) const; + + bool ThisSnapperMightSnap() const; + bool GuidesMightSnap() const; + + Geom::Coord getSnapperTolerance() const; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom) + bool getSnapperAlwaysSnap() const; //if true, then the snapper will always snap, regardless of its tolerance + + void freeSnap(SnappedConstraints &sc, + Inkscape::SnapPreferences::PointType const &t, + Inkscape::SnapCandidatePoint const &p, + Geom::OptRect const &bbox_to_snap, + std::vector<SPItem const *> const *it, + std::vector<SnapCandidatePoint> *unselected_nodes) const; + + void constrainedSnap(SnappedConstraints &sc, + Inkscape::SnapPreferences::PointType const &t, + Inkscape::SnapCandidatePoint const &p, + Geom::OptRect const &bbox_to_snap, + ConstraintLine const &c, + std::vector<SPItem const *> const *it) const; private: //store some lists of candidates, points and paths, so we don't have to rebuild them for each point we want to snap - std::vector<SnapCandidate> *_candidates; - std::vector<std::pair<Geom::Point, int> > *_points_to_snap_to; - std::vector<std::pair<Geom::PathVector*, SnapTargetType> > *_paths_to_snap_to; + std::vector<SnapCandidateItem> *_candidates; + std::vector<SnapCandidatePoint> *_points_to_snap_to; + std::vector<SnapCandidatePath > *_paths_to_snap_to; void _findCandidates(SPObject* parent, std::vector<SPItem const *> const *it, @@ -104,10 +84,8 @@ private: void _snapNodes(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, // in desktop coordinates - SnapSourceType const &source_type, - bool const &first_point, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes) const; // in desktop coordinates + Inkscape::SnapCandidatePoint const &p, + std::vector<SnapCandidatePoint> *unselected_nodes) const; // in desktop coordinates void _snapTranslatingGuideToNodes(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, @@ -119,20 +97,16 @@ private: void _snapPaths(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, // in desktop coordinates - SnapSourceType const &source_type, - bool const &first_point, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes, // in desktop coordinates + Inkscape::SnapCandidatePoint const &p, // in desktop coordinates + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, // in desktop coordinates SPPath const *selected_path) const; void _snapPathsConstrained(SnappedConstraints &sc, Inkscape::SnapPreferences::PointType const &t, - Geom::Point const &p, // in desktop coordinates - SnapSourceType const source_type, - bool const &first_point, + Inkscape::SnapCandidatePoint const &p, // in desktop coordinates ConstraintLine const &c) const; - bool isUnselectedNode(Geom::Point const &point, std::vector<std::pair<Geom::Point, int> > const *unselected_nodes) const; + bool isUnselectedNode(Geom::Point const &point, std::vector<Inkscape::SnapCandidatePoint> const *unselected_nodes) const; void _collectPaths(Inkscape::SnapPreferences::PointType const &t, bool const &first_point) const; @@ -140,11 +114,11 @@ private: void _clear_paths() const; Geom::PathVector* _getBorderPathv() const; Geom::PathVector* _getPathvFromRect(Geom::Rect const rect) const; - void _getBorderNodes(std::vector<std::pair<Geom::Point, int> > *points) const; + void _getBorderNodes(std::vector<SnapCandidatePoint> *points) const; }; // end of ObjectSnapper class -void getBBoxPoints(Geom::OptRect const bbox, std::vector<std::pair<Geom::Point, int> > *points, bool const isTarget, bool const includeCorners, bool const includeLineMidpoints, bool const includeObjectMidpoints); +void getBBoxPoints(Geom::OptRect const bbox, std::vector<SnapCandidatePoint> *points, bool const isTarget, bool const includeCorners, bool const includeLineMidpoints, bool const includeObjectMidpoints); } // end of namespace Inkscape diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp index 99ee78ade..50d26ba64 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -86,12 +86,20 @@ sp_selected_path_combine(SPDesktop *desktop) SPItem *first = NULL; Inkscape::XML::Node *parent = NULL; + if (did) { + selection->clear(); + } + for (GSList *i = items; i != NULL; i = i->next) { // going from top to bottom SPItem *item = (SPItem *) i->data; if (!SP_IS_PATH(item)) continue; - did = true; + + if (!did) { + selection->clear(); + did = true; + } SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(item)); if (first == NULL) { // this is the topmost path @@ -124,11 +132,8 @@ sp_selected_path_combine(SPDesktop *desktop) g_slist_free(items); if (did) { - selection->clear(); - - // delete the topmost one so that its clones don't get alerted; this object will be - // restored shortly, with the same id SP_OBJECT(first)->deleteObject(false); + // delete the topmost. Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc()); Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); diff --git a/src/persp3d-reference.h b/src/persp3d-reference.h index 43b0e82b1..7c2ce31bf 100644 --- a/src/persp3d-reference.h +++ b/src/persp3d-reference.h @@ -12,9 +12,9 @@ #include "uri-references.h" #include <sigc++/sigc++.h> +#include "persp3d.h" class SPObject; -class Persp3D; namespace Inkscape { namespace XML { @@ -28,7 +28,7 @@ public: ~Persp3DReference(); Persp3D *getObject() const { - return (Persp3D *)URIReference::getObject(); + return SP_PERSP3D(URIReference::getObject()); } SPObject *owner; diff --git a/src/persp3d.cpp b/src/persp3d.cpp index 916e9f25f..6a697ec9b 100644 --- a/src/persp3d.cpp +++ b/src/persp3d.cpp @@ -24,7 +24,7 @@ #include <glibmm/i18n.h> static void persp3d_class_init(Persp3DClass *klass); -static void persp3d_init(Persp3D *stop); +static void persp3d_init(Persp3D *persp); static void persp3d_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); static void persp3d_release(SPObject *object); @@ -34,10 +34,22 @@ static Inkscape::XML::Node *persp3d_write(SPObject *object, Inkscape::XML::Docum static void persp3d_on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data); +static void persp3d_update_with_point (Persp3DImpl *persp_impl, Proj::Axis const axis, Proj::Pt2 const &new_image); +static gchar * persp3d_pt_to_str (Persp3DImpl *persp_impl, Proj::Axis const axis); + static SPObjectClass *persp3d_parent_class; static int global_counter = 0; +/* Constructor/destructor for the internal class */ + +Persp3DImpl::Persp3DImpl() { + tmat = Proj::TransfMat3x4 (); + document = NULL; + + my_counter = global_counter++; +} + /** * Registers Persp3d class and returns its type. */ @@ -91,13 +103,7 @@ static void persp3d_class_init(Persp3DClass *klass) static void persp3d_init(Persp3D *persp) { - persp->tmat = Proj::TransfMat3x4 (); - - persp->boxes_transformed = new std::map<SPBox3D *, bool>; - persp->boxes_transformed->clear(); - persp->document = NULL; - - persp->my_counter = global_counter++; + persp->perspective_impl = new Persp3DImpl(); } /** @@ -124,8 +130,8 @@ static void persp3d_build(SPObject *object, SPDocument *document, Inkscape::XML: * Virtual release of Persp3D members before destruction. */ static void persp3d_release(SPObject *object) { - Persp3D *persp = SP_PERSP3D (object); - delete persp->boxes_transformed; + Persp3D *persp = SP_PERSP3D(object); + delete persp->perspective_impl; SP_OBJECT_REPR(object)->removeListenerByData(object); } @@ -138,34 +144,34 @@ static void persp3d_release(SPObject *object) { static void persp3d_set(SPObject *object, unsigned key, gchar const *value) { - Persp3D *persp = SP_PERSP3D (object); + Persp3DImpl *persp_impl = SP_PERSP3D(object)->perspective_impl; switch (key) { case SP_ATTR_INKSCAPE_PERSP3D_VP_X: { if (value) { Proj::Pt2 new_image (value); - persp3d_update_with_point (persp, Proj::X, new_image); + persp3d_update_with_point (persp_impl, Proj::X, new_image); } break; } case SP_ATTR_INKSCAPE_PERSP3D_VP_Y: { if (value) { Proj::Pt2 new_image (value); - persp3d_update_with_point (persp, Proj::Y, new_image); + persp3d_update_with_point (persp_impl, Proj::Y, new_image); break; } } case SP_ATTR_INKSCAPE_PERSP3D_VP_Z: { if (value) { Proj::Pt2 new_image (value); - persp3d_update_with_point (persp, Proj::Z, new_image); + persp3d_update_with_point (persp_impl, Proj::Z, new_image); break; } } case SP_ATTR_INKSCAPE_PERSP3D_ORIGIN: { if (value) { Proj::Pt2 new_image (value); - persp3d_update_with_point (persp, Proj::W, new_image); + persp3d_update_with_point (persp_impl, Proj::W, new_image); break; } } @@ -201,36 +207,40 @@ persp3d_update(SPObject *object, SPCtx *ctx, guint flags) } Persp3D * -persp3d_create_xml_element (SPDocument *document, Persp3D *dup) {// if dup is given, copy the attributes over +persp3d_create_xml_element (SPDocument *document, Persp3DImpl *dup) {// if dup is given, copy the attributes over SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); Inkscape::XML::Node *repr; - if (dup) { - repr = SP_OBJECT_REPR(dup)->duplicate (xml_doc); - } else { - /* if no perspective is given, create a default one */ - repr = xml_doc->createElement("inkscape:perspective"); - repr->setAttribute("sodipodi:type", "inkscape:persp3d"); - Proj::Pt2 proj_vp_x = Proj::Pt2 (0.0, sp_document_height(document)/2, 1.0); - Proj::Pt2 proj_vp_y = Proj::Pt2 ( 0.0,1000.0, 0.0); - Proj::Pt2 proj_vp_z = Proj::Pt2 (sp_document_width(document), sp_document_height(document)/2, 1.0); - Proj::Pt2 proj_origin = Proj::Pt2 (sp_document_width(document)/2, sp_document_height(document)/3, 1.0); + /* if no perspective is given, create a default one */ + repr = xml_doc->createElement("inkscape:perspective"); + repr->setAttribute("sodipodi:type", "inkscape:persp3d"); - gchar *str = NULL; - str = proj_vp_x.coord_string(); - repr->setAttribute("inkscape:vp_x", str); - g_free (str); - str = proj_vp_y.coord_string(); - repr->setAttribute("inkscape:vp_y", str); - g_free (str); - str = proj_vp_z.coord_string(); - repr->setAttribute("inkscape:vp_z", str); - g_free (str); - str = proj_origin.coord_string(); - repr->setAttribute("inkscape:persp3d-origin", str); - g_free (str); - } + Proj::Pt2 proj_vp_x = Proj::Pt2 (0.0, sp_document_height(document)/2, 1.0); + Proj::Pt2 proj_vp_y = Proj::Pt2 (0.0, 1000.0, 0.0); + Proj::Pt2 proj_vp_z = Proj::Pt2 (sp_document_width(document), sp_document_height(document)/2, 1.0); + Proj::Pt2 proj_origin = Proj::Pt2 (sp_document_width(document)/2, sp_document_height(document)/3, 1.0); + + if (dup) { + proj_vp_x = dup->tmat.column (Proj::X); + proj_vp_y = dup->tmat.column (Proj::Y); + proj_vp_z = dup->tmat.column (Proj::Z); + proj_origin = dup->tmat.column (Proj::W); + } + + gchar *str = NULL; + str = proj_vp_x.coord_string(); + repr->setAttribute("inkscape:vp_x", str); + g_free (str); + str = proj_vp_y.coord_string(); + repr->setAttribute("inkscape:vp_y", str); + g_free (str); + str = proj_vp_z.coord_string(); + repr->setAttribute("inkscape:vp_z", str); + g_free (str); + str = proj_origin.coord_string(); + repr->setAttribute("inkscape:persp3d-origin", str); + g_free (str); /* Append the new persp3d to defs */ SP_OBJECT_REPR(defs)->addChild(repr, NULL); @@ -258,7 +268,7 @@ persp3d_document_first_persp (SPDocument *document) { static Inkscape::XML::Node * persp3d_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { - Persp3D *persp = SP_PERSP3D(object); + Persp3DImpl *persp_impl = SP_PERSP3D(object)->perspective_impl; if ((flags & SP_OBJECT_WRITE_BUILD & SP_OBJECT_WRITE_EXT) && !repr) { // this is where we end up when saving as plain SVG (also in other circumstances?); @@ -268,16 +278,16 @@ persp3d_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML: if (flags & SP_OBJECT_WRITE_EXT) { gchar *str = NULL; // FIXME: Should this be freed each time we set an attribute or only in the end or at all? - str = persp3d_pt_to_str (persp, Proj::X); + str = persp3d_pt_to_str (persp_impl, Proj::X); repr->setAttribute("inkscape:vp_x", str); - str = persp3d_pt_to_str (persp, Proj::Y); + str = persp3d_pt_to_str (persp_impl, Proj::Y); repr->setAttribute("inkscape:vp_y", str); - str = persp3d_pt_to_str (persp, Proj::Z); + str = persp3d_pt_to_str (persp_impl, Proj::Z); repr->setAttribute("inkscape:vp_z", str); - str = persp3d_pt_to_str (persp, Proj::W); + str = persp3d_pt_to_str (persp_impl, Proj::W); repr->setAttribute("inkscape:persp3d-origin", str); } @@ -289,7 +299,7 @@ persp3d_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML: /* convenience wrapper around persp3d_get_finite_dir() and persp3d_get_infinite_dir() */ Geom::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis) { - if (persp3d_VP_is_finite(persp, axis)) { + if (persp3d_VP_is_finite(persp->perspective_impl, axis)) { return persp3d_get_finite_dir(persp, pt, axis); } else { return persp3d_get_infinite_dir(persp, axis); @@ -314,17 +324,17 @@ persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis) { double persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis) { - return persp->tmat.get_infinite_angle(axis); + return persp->perspective_impl->tmat.get_infinite_angle(axis); } bool -persp3d_VP_is_finite (Persp3D *persp, Proj::Axis axis) { - return persp->tmat.has_finite_image(axis); +persp3d_VP_is_finite (Persp3DImpl *persp_impl, Proj::Axis axis) { + return persp_impl->tmat.has_finite_image(axis); } void persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo) { - persp->tmat.toggle_finite(axis); + persp->perspective_impl->tmat.toggle_finite(axis); // FIXME: Remove this repr update and rely on vp_drag_sel_modified() to do this for us // On the other hand, vp_drag_sel_modified() would update all boxes; // here we can confine ourselves to the boxes of this particular perspective. @@ -348,7 +358,7 @@ persp3d_toggle_VPs (std::list<Persp3D *> p, Proj::Axis axis) { void persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state) { - if (persp3d_VP_is_finite(persp, axis) != (state == Proj::VP_FINITE)) { + if (persp3d_VP_is_finite(persp->perspective_impl, axis) != (state == Proj::VP_FINITE)) { persp3d_toggle_VP(persp, axis); } } @@ -356,62 +366,67 @@ persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state) { void persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed) { // angle is in degrees // FIXME: Most of this functionality should be moved to trans_mat_3x4.(h|cpp) - if (persp->tmat.has_finite_image(axis)) { + if (persp->perspective_impl->tmat.has_finite_image(axis)) { // don't rotate anything for finite VPs return; } - Proj::Pt2 v_dir_proj (persp->tmat.column(axis)); + Proj::Pt2 v_dir_proj (persp->perspective_impl->tmat.column(axis)); Geom::Point v_dir (v_dir_proj[0], v_dir_proj[1]); double a = Geom::atan2 (v_dir) * 180/M_PI; a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle - persp->tmat.set_infinite_direction (axis, a); + persp->perspective_impl->tmat.set_infinite_direction (axis, a); persp3d_update_box_reprs (persp); SP_OBJECT(persp)->updateRepr(SP_OBJECT_WRITE_EXT); } void -persp3d_update_with_point (Persp3D *persp, Proj::Axis const axis, Proj::Pt2 const &new_image) { - persp->tmat.set_image_pt (axis, new_image); +persp3d_update_with_point (Persp3DImpl *persp_impl, Proj::Axis const axis, Proj::Pt2 const &new_image) { + persp_impl->tmat.set_image_pt (axis, new_image); } void persp3d_apply_affine_transformation (Persp3D *persp, Geom::Matrix const &xform) { - persp->tmat *= xform; + persp->perspective_impl->tmat *= xform; persp3d_update_box_reprs(persp); SP_OBJECT(persp)->updateRepr(SP_OBJECT_WRITE_EXT); } gchar * -persp3d_pt_to_str (Persp3D *persp, Proj::Axis const axis) +persp3d_pt_to_str (Persp3DImpl *persp_impl, Proj::Axis const axis) { - return persp->tmat.pt_to_str(axis); + return persp_impl->tmat.pt_to_str(axis); } void persp3d_add_box (Persp3D *persp, SPBox3D *box) { + Persp3DImpl *persp_impl = persp->perspective_impl; + if (!box) { return; } - if (std::find (persp->boxes.begin(), persp->boxes.end(), box) != persp->boxes.end()) { + if (std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box) != persp_impl->boxes.end()) { return; } - persp->boxes.push_back(box); + persp_impl->boxes.push_back(box); } void persp3d_remove_box (Persp3D *persp, SPBox3D *box) { - std::vector<SPBox3D *>::iterator i = std::find (persp->boxes.begin(), persp->boxes.end(), box); - if (i != persp->boxes.end()) { - persp->boxes.erase(i); - } + Persp3DImpl *persp_impl = persp->perspective_impl; + + std::vector<SPBox3D *>::iterator i = std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box); + if (i != persp_impl->boxes.end()) + persp_impl->boxes.erase(i); } bool persp3d_has_box (Persp3D *persp, SPBox3D *box) { + Persp3DImpl *persp_impl = persp->perspective_impl; + // FIXME: For some reason, std::find() does not seem to compare pointers "correctly" (or do we need to // provide a proper comparison function?), so we manually traverse the list. - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { if ((*i) == box) { return true; } @@ -420,86 +435,27 @@ persp3d_has_box (Persp3D *persp, SPBox3D *box) { } void -persp3d_add_box_transform (Persp3D *persp, SPBox3D *box) { - std::map<SPBox3D *, bool>::iterator i = persp->boxes_transformed->find(box); - if (i != persp->boxes_transformed->end() && (*i).second == true) { - g_print ("Warning! In %s (%d): trying to add transform status for box %d twice when it's already listed as true.\n", SP_OBJECT_REPR(persp)->attribute("id"), persp->my_counter, box->my_counter); - return; - } - - (*persp->boxes_transformed)[box] = false; -} - -void -persp3d_remove_box_transform (Persp3D *persp, SPBox3D *box) { - persp->boxes_transformed->erase(box); -} +persp3d_update_box_displays (Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; -void -persp3d_set_box_transformed (Persp3D *persp, SPBox3D *box, bool transformed) { - if (persp->boxes_transformed->find(box) == persp->boxes_transformed->end()) { - g_print ("Warning! In %s (%d): trying to set transform status for box %d, but it is not listed in the perspective!! Aborting.\n", - SP_OBJECT_REPR(persp)->attribute("id"), persp->my_counter, - box->my_counter); + if (persp_impl->boxes.empty()) return; - } - - (*persp->boxes_transformed)[box] = transformed; -} - -bool -persp3d_was_transformed (Persp3D *persp) { - if (persp->boxes_transformed->size() == 1) { - /* either the transform has not been applied to the single box associated to this perspective yet - or the transform was already reset; in both cases we need to return false because upcoming - transforms need to be applied */ - (*persp->boxes_transformed->begin()).second = false; // make sure the box is marked as untransformed (in case more boxes are added later) - return false; - } - - for (std::map<SPBox3D *, bool>::iterator i = persp->boxes_transformed->begin(); - i != persp->boxes_transformed->end(); ++i) { - if ((*i).second == true) { - // at least one of the boxes in the perspective has already been transformed; - return true; - } - } - return false; // all boxes in the perspective are still untransformed; a pending transformation should be applied -} - -bool -persp3d_all_transformed(Persp3D *persp) { - for (std::map<SPBox3D *, bool>::iterator i = persp->boxes_transformed->begin(); - i != persp->boxes_transformed->end(); ++i) { - if ((*i).second == false) { - return false; - } - } - return true; -} - -void -persp3d_unset_transforms(Persp3D *persp) { - for (std::map<SPBox3D *, bool>::iterator i = persp->boxes_transformed->begin(); - i != persp->boxes_transformed->end(); ++i) { - (*i).second = false; + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + box3d_position_set(*i); } } void -persp3d_update_box_displays (Persp3D *persp) { - if (persp->boxes.empty()) +persp3d_update_box_reprs (Persp3D *persp) { + if (!persp) { + // Hmm, is it an error if this happens? return; - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { - box3d_position_set(*i); } -} + Persp3DImpl *persp_impl = persp->perspective_impl; -void -persp3d_update_box_reprs (Persp3D *persp) { - if (persp->boxes.empty()) + if (persp_impl->boxes.empty()) return; - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { SP_OBJECT(*i)->updateRepr(SP_OBJECT_WRITE_EXT); box3d_set_z_orders(*i); } @@ -507,9 +463,11 @@ persp3d_update_box_reprs (Persp3D *persp) { void persp3d_update_z_orders (Persp3D *persp) { - if (persp->boxes.empty()) + Persp3DImpl *persp_impl = persp->perspective_impl; + + if (persp_impl->boxes.empty()) return; - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { box3d_set_z_orders(*i); } } @@ -519,8 +477,10 @@ persp3d_update_z_orders (Persp3D *persp) { // obsolete. We should do this. std::list<SPBox3D *> persp3d_list_of_boxes(Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; + std::list<SPBox3D *> bx_lst; - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { bx_lst.push_back(*i); } return bx_lst; @@ -529,7 +489,7 @@ persp3d_list_of_boxes(Persp3D *persp) { bool persp3d_perspectives_coincide(const Persp3D *lhs, const Persp3D *rhs) { - return lhs->tmat == rhs->tmat; + return lhs->perspective_impl->tmat == rhs->perspective_impl->tmat; } void @@ -566,10 +526,12 @@ persp3d_on_repr_attr_changed ( Inkscape::XML::Node * /*repr*/, /* checks whether all boxes linked to this perspective are currently selected */ bool -persp3d_has_all_boxes_in_selection (Persp3D *persp) { - std::list<SPBox3D *> selboxes = sp_desktop_selection(inkscape_active_desktop())->box3DList(); +persp3d_has_all_boxes_in_selection (Persp3D *persp, Inkscape::Selection *selection) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + std::list<SPBox3D *> selboxes = selection->box3DList(); - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { if (std::find(selboxes.begin(), selboxes.end(), *i) == selboxes.end()) { // we have an unselected box in the perspective return false; @@ -578,62 +540,12 @@ persp3d_has_all_boxes_in_selection (Persp3D *persp) { return true; } -/** - * For each perspective having a box in \a selection, determine all its unselected boxes. - */ -// TODO: Check where we can use pass-by-reference (or so) instead of recreating all the lists afresh. -std::map<Persp3D *, std::list<SPBox3D *> > -persp3d_unselected_boxes(Inkscape::Selection *selection) { - std::list<Persp3D *> plist = selection->perspList(); - std::map<Persp3D *, std::list<SPBox3D *> > punsel; - - std::list<Persp3D *>::iterator i; - std::vector<SPBox3D *>::iterator j; - // for all perspectives in the list ... - for (i = plist.begin(); i != plist.end(); ++i) { - Persp3D *persp = *i; - // ... and each box associated to it ... - for (j = persp->boxes.begin(); j != persp->boxes.end(); ++j) { - SPBox3D *box = *j; - // ... check whether it is unselected, and if so add it to the list - if (persp->boxes_transformed->find(box) == persp->boxes_transformed->end()) { - punsel[persp].push_back(box); - } - } - } - return punsel; -} - -/** - * Split all perspectives with a box in \a selection by moving their unselected boxes to newly - * created perspectives. - */ -void -persp3d_split_perspectives_according_to_selection(Inkscape::Selection *selection) { - std::map<Persp3D *, std::list<SPBox3D *> > punsel = persp3d_unselected_boxes(selection); - - std::map<Persp3D *, std::list<SPBox3D *> >::iterator i; - std::list<SPBox3D *>::iterator j; - // for all perspectives in the list ... - for (i = punsel.begin(); i != punsel.end(); ++i) { - Persp3D *persp = (*i).first; - // ... if the perspective has unselected boxes ... - if (!(*i).second.empty()) { - // create a new perspective and move these boxes over - Persp3D * new_persp = persp3d_create_xml_element (SP_OBJECT_DOCUMENT(persp), persp); - for (j = (*i).second.begin(); j != (*i).second.end(); ++j) { - SPBox3D *box = *j; - box3d_switch_perspectives(box, persp, new_persp); - } - } - } -} - /* some debugging stuff follows */ void persp3d_print_debugging_info (Persp3D *persp) { - g_print ("=== Info for Persp3D %d ===\n", persp->my_counter); + Persp3DImpl *persp_impl = persp->perspective_impl; + g_print ("=== Info for Persp3D %d ===\n", persp_impl->my_counter); gchar * cstr; for (int i = 0; i < 4; ++i) { cstr = persp3d_get_VP(persp, Proj::axes[i]).coord_string(); @@ -645,8 +557,8 @@ persp3d_print_debugging_info (Persp3D *persp) { g_free(cstr); g_print (" Boxes: "); - for (std::vector<SPBox3D *>::iterator i = persp->boxes.begin(); i != persp->boxes.end(); ++i) { - g_print ("%d (%d) ", (*i)->my_counter, box3d_get_perspective(*i)->my_counter); + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + g_print ("%d (%d) ", (*i)->my_counter, box3d_get_perspective(*i)->perspective_impl->my_counter); } g_print ("\n"); g_print ("========================\n"); @@ -674,16 +586,23 @@ persp3d_print_all_selected() { for (std::list<Persp3D *>::iterator j = sel_persps.begin(); j != sel_persps.end(); ++j) { Persp3D *persp = SP_PERSP3D(*j); - g_print (" %s (%d): ", SP_OBJECT_REPR(persp)->attribute("id"), persp->my_counter); - for (std::map<SPBox3D *, bool>::iterator i = persp->boxes_transformed->begin(); - i != persp->boxes_transformed->end(); ++i) { - g_print ("<%d,%d> ", (*i).first->my_counter, (*i).second); + Persp3DImpl *persp_impl = persp->perspective_impl; + g_print (" %s (%d): ", SP_OBJECT_REPR(persp)->attribute("id"), persp->perspective_impl->my_counter); + for (std::vector<SPBox3D *>::iterator i = persp_impl->boxes.begin(); + i != persp_impl->boxes.end(); ++i) { + g_print ("%d ", (*i)->my_counter); } g_print ("\n"); } g_print ("======================================\n\n"); } +void print_current_persp3d(gchar *func_name, Persp3D *persp) { + g_print ("%s: current_persp3d is now %s\n", + func_name, + persp ? SP_OBJECT_REPR(persp)->attribute("id") : "NULL"); +} + /* Local Variables: mode:c++ diff --git a/src/persp3d.h b/src/persp3d.h index 79bec0232..62cc586ef 100644 --- a/src/persp3d.h +++ b/src/persp3d.h @@ -29,16 +29,25 @@ class SPBox3D; class Box3DContext; -struct Persp3D : public SPObject { +class Persp3DImpl { +public: + Persp3DImpl(); + +//private: Proj::TransfMat3x4 tmat; // Also write the list of boxes into the xml repr and vice versa link boxes to their persp3d? std::vector<SPBox3D *> boxes; - std::map<SPBox3D *, bool>* boxes_transformed; // TODO: eventually we should merge this with 'boxes' - SPDocument *document; // should this rather be the SPDesktop? + SPDocument *document; // for debugging only int my_counter; + +// friend class Persp3D; +}; + +struct Persp3D : public SPObject { + Persp3DImpl *perspective_impl; }; struct Persp3DClass { @@ -54,52 +63,43 @@ inline Persp3D * persp3d_get_from_repr (Inkscape::XML::Node *repr) { return SP_PERSP3D(SP_ACTIVE_DOCUMENT->getObjectByRepr(repr)); } inline Proj::Pt2 persp3d_get_VP (Persp3D *persp, Proj::Axis axis) { - return persp->tmat.column(axis); + return persp->perspective_impl->tmat.column(axis); } Geom::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis); // convenience wrapper around the following two Geom::Point persp3d_get_finite_dir (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis); Geom::Point persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis); double persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis); -bool persp3d_VP_is_finite (Persp3D *persp, Proj::Axis axis); +bool persp3d_VP_is_finite (Persp3DImpl *persp_impl, Proj::Axis axis); void persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo = true); void persp3d_toggle_VPs (std::list<Persp3D *>, Proj::Axis axis); void persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state); void persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed); // angle is in degrees -void persp3d_update_with_point (Persp3D *persp, Proj::Axis const axis, Proj::Pt2 const &new_image); void persp3d_apply_affine_transformation (Persp3D *persp, Geom::Matrix const &xform); -gchar * persp3d_pt_to_str (Persp3D *persp, Proj::Axis const axis); void persp3d_add_box (Persp3D *persp, SPBox3D *box); void persp3d_remove_box (Persp3D *persp, SPBox3D *box); bool persp3d_has_box (Persp3D *persp, SPBox3D *box); -void persp3d_add_box_transform (Persp3D *persp, SPBox3D *box); -void persp3d_remove_box_transform (Persp3D *persp, SPBox3D *box); -void persp3d_set_box_transformed (Persp3D *persp, SPBox3D *box, bool transformed = true); -bool persp3d_was_transformed (Persp3D *persp); -bool persp3d_all_transformed(Persp3D *persp); -void persp3d_unset_transforms(Persp3D *persp); - void persp3d_update_box_displays (Persp3D *persp); void persp3d_update_box_reprs (Persp3D *persp); void persp3d_update_z_orders (Persp3D *persp); -inline unsigned int persp3d_num_boxes (Persp3D *persp) { return persp->boxes.size(); } +inline unsigned int persp3d_num_boxes (Persp3D *persp) { return persp->perspective_impl->boxes.size(); } std::list<SPBox3D *> persp3d_list_of_boxes(Persp3D *persp); bool persp3d_perspectives_coincide(const Persp3D *lhs, const Persp3D *rhs); void persp3d_absorb(Persp3D *persp1, Persp3D *persp2); -Persp3D * persp3d_create_xml_element (SPDocument *document, Persp3D *dup = NULL); +Persp3D * persp3d_create_xml_element (SPDocument *document, Persp3DImpl *dup = NULL); Persp3D * persp3d_document_first_persp (SPDocument *document); -bool persp3d_has_all_boxes_in_selection (Persp3D *persp); -std::map<Persp3D *, std::list<SPBox3D *> > persp3d_unselected_boxes(Inkscape::Selection *selection); -void persp3d_split_perspectives_according_to_selection(Inkscape::Selection *selection); +bool persp3d_has_all_boxes_in_selection (Persp3D *persp, Inkscape::Selection *selection); void persp3d_print_debugging_info (Persp3D *persp); void persp3d_print_debugging_info_all(SPDocument *doc); void persp3d_print_all_selected(); +void print_current_persp3d(gchar *func_name, Persp3D *persp); + #endif /* __PERSP3D_H__ */ /* diff --git a/src/pixmaps/cursor-spray-move.xpm b/src/pixmaps/cursor-spray-move.xpm new file mode 100644 index 000000000..ad898b3a5 --- /dev/null +++ b/src/pixmaps/cursor-spray-move.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char const * cursor_spray_move_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ", +" ... ", +" ... ... ... ... ", +" ..+. ..+. ..+. ..+. ", +" .+++ .+++ .+++ .+++ ", +" ..+. .+++ .+++ ..+ ", +" .... .+++ .+++ .... ", +" ..+. ..+ ..+ ..+. ", +" .+++ ..+. ..+. .+++ ", +" ..+ .+++ .+++ ..+ ", +" .... ..+ ..+ .... ", +" ..+. .... .... ..+. ", +" .+++ ..+. ..+. .+++ ", +" ..+ .+++ .+++ ..+ ", +" ..+ ..+ ", +" ... ... ", +" ..+. ..+. ", +" .+++ .+++ ", +" ..+ ..+ ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/cursor-spray.xpm b/src/pixmaps/cursor-spray.xpm new file mode 100644 index 000000000..9ccefee4f --- /dev/null +++ b/src/pixmaps/cursor-spray.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char const * cursor_spray_xpm[] = { +"32 32 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ... ", +" .+. ", +" .+. ", +"....+.... ", +".+++ +++. ", +"....+.... ", +" .+. ", +" .+. ", +" ... .+. +. ", +" .+.+.+.+. ", +" .+...+...+. ", +" +...+.....+. ", +" . . . .+.+.......+. ", +" + + + .+.........+. ", +" . . . .+...........+. ", +" + + .+.............+. ", +" . . . .+.............+. ", +" + + + .+.............+. ", +" . . .+.............+. ", +" .+.............+. ", +" .+.............+. ", +" .+...........+. ", +" .+.........+. ", +" .+.......+. ", +" .+.....+. ", +" .+...+. ", +" .+.+. ", +" .+. ", +" . ", +" ", +" ", +" "}; diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 075d2b031..90fc85757 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -111,9 +111,11 @@ static char const preferences_skeleton[] = " </eventcontext>\n" " <eventcontext id=\"text\" usecurrent=\"0\" gradientdrag=\"1\"\n" " font_sample=\"AaBbCcIiPpQq12369$\342\202\254\302\242?.;/()\"\n" +" show_sample_in_list=\"1\"\n" " style=\"fill:black;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;font-style:normal;font-weight:normal;font-size:40px;\" selcue=\"1\"/>\n" " <eventcontext id=\"nodes\" selcue=\"1\" gradientdrag=\"1\" highlight_color=\"4278190335\" pathflash_enabled=\"1\" pathflash_unselected=\"0\" pathflash_timeout=\"500\" show_handles=\"1\" show_outline=\"0\" sculpting_profile=\"1\" />\n" " <eventcontext id=\"tweak\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n" +" <eventcontext id=\"spray\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n" " <eventcontext id=\"gradient\" selcue=\"1\"/>\n" " <eventcontext id=\"zoom\" selcue=\"1\" gradientdrag=\"0\"/>\n" " <eventcontext id=\"dropper\" selcue=\"1\" gradientdrag=\"1\" pick=\"1\" setalpha=\"1\"/>\n" @@ -338,6 +340,7 @@ static char const preferences_skeleton[] = " </group>\n" " <group id=\"workarounds\"\n" " colorsontop=\"0\"/>\n" +" <group id=\"threading\" numthreads=\"1\"/>\n" " </group>\n" "\n" " <group id=\"extensions\">" diff --git a/src/print.cpp b/src/print.cpp index 044dffe34..ed9b8d19c 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -130,6 +130,7 @@ sp_print_document(Gtk::Window& parentWindow, SPDocument *doc) SPItem *base = SP_ITEM(sp_document_root(doc)); NRArena *arena = NRArena::create(); unsigned int dkey = sp_item_display_key_new(1); + // TODO investigate why we are grabbing root and then ignoring it. NRArenaItem *root = sp_item_invoke_show(base, arena, dkey, SP_ITEM_SHOW_DISPLAY); // Run print dialog diff --git a/src/satisfied-guide-cns.cpp b/src/satisfied-guide-cns.cpp index 505e18675..dcf635989 100644 --- a/src/satisfied-guide-cns.cpp +++ b/src/satisfied-guide-cns.cpp @@ -6,14 +6,14 @@ #include <approx-equal.h> void satisfied_guide_cns(SPDesktop const &desktop, - SnapPointsWithType const &snappoints, + std::vector<Inkscape::SnapCandidatePoint> const &snappoints, std::vector<SPGuideConstraint> &cns) { SPNamedView const &nv = *sp_desktop_namedview(&desktop); for (GSList const *l = nv.guides; l != NULL; l = l->next) { SPGuide &g = *SP_GUIDE(l->data); for (unsigned int i = 0; i < snappoints.size(); ++i) { - if (approx_equal( sp_guide_distance_from_pt(&g, snappoints[i].first), 0) ) { + if (approx_equal( sp_guide_distance_from_pt(&g, snappoints[i].getPoint()), 0) ) { cns.push_back(SPGuideConstraint(&g, i)); } } diff --git a/src/satisfied-guide-cns.h b/src/satisfied-guide-cns.h index 99229f64c..7fba29161 100644 --- a/src/satisfied-guide-cns.h +++ b/src/satisfied-guide-cns.h @@ -9,7 +9,7 @@ class SPGuideConstraint; void satisfied_guide_cns(SPDesktop const &desktop, - SnapPointsWithType const &snappoints, + std::vector<Inkscape::SnapCandidatePoint> const &snappoints, std::vector<SPGuideConstraint> &cns); diff --git a/src/selcue.cpp b/src/selcue.cpp index 714daaa7e..8756524dd 100644 --- a/src/selcue.cpp +++ b/src/selcue.cpp @@ -181,18 +181,20 @@ void Inkscape::SelCue::_newTextBaselines() if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { // visualize baseline Inkscape::Text::Layout const *layout = te_get_layout(item); if (layout != NULL && layout->outputExists()) { - Geom::Point a = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(item); - baseline_point = sp_canvas_item_new(sp_desktop_controls(_desktop), SP_TYPE_CTRL, - "mode", SP_CTRL_MODE_XOR, - "size", 4.0, - "filled", 0, - "stroked", 1, - "stroke_color", 0x000000ff, - NULL); - - sp_canvas_item_show(baseline_point); - SP_CTRL(baseline_point)->moveto(a); - sp_canvas_item_move_to_z(baseline_point, 0); + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + if (pt) { + baseline_point = sp_canvas_item_new(sp_desktop_controls(_desktop), SP_TYPE_CTRL, + "mode", SP_CTRL_MODE_XOR, + "size", 4.0, + "filled", 0, + "stroked", 1, + "stroke_color", 0x000000ff, + NULL); + + sp_canvas_item_show(baseline_point); + SP_CTRL(baseline_point)->moveto((*pt) * sp_item_i2d_affine(item)); + sp_canvas_item_move_to_z(baseline_point, 0); + } } } diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index a9736e991..d4978af3b 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -76,6 +76,7 @@ #include "helper/units.h" #include "sp-item.h" #include "box3d.h" +#include "persp3d.h" #include "unit-constants.h" #include "xml/simple-document.h" #include "sp-filter-reference.h" @@ -1169,7 +1170,6 @@ selection_contains_both_clone_and_original(Inkscape::Selection *selection) return clone_with_original; } - /** Apply matrix to the selection. \a set_i2d is normally true, which means objects are in the original transform, synced with their reprs, and need to jump to the new transform in one go. A value of set_i2d==false is only used by seltrans when it's dragging objects live (not outlines); in @@ -1181,6 +1181,29 @@ void sp_selection_apply_affine(Inkscape::Selection *selection, Geom::Matrix cons if (selection->isEmpty()) return; + // For each perspective with a box in selection, check whether all boxes are selected and + // unlink all non-selected boxes. + Persp3D *persp; + Persp3D *transf_persp; + std::list<Persp3D *> plist = selection->perspList(); + for (std::list<Persp3D *>::iterator i = plist.begin(); i != plist.end(); ++i) { + persp = (Persp3D *) (*i); + + if (!persp3d_has_all_boxes_in_selection (persp, selection)) { + std::list<SPBox3D *> selboxes = selection->box3DList(persp); + + // create a new perspective as a copy of the current one and link the selected boxes to it + transf_persp = persp3d_create_xml_element (SP_OBJECT_DOCUMENT(persp), persp->perspective_impl); + + for (std::list<SPBox3D *>::iterator b = selboxes.begin(); b != selboxes.end(); ++b) + box3d_switch_perspectives(*b, persp, transf_persp); + } else { + transf_persp = persp; + } + + persp3d_apply_affine_transformation(transf_persp, affine); + } + for (GSList const *l = selection->itemList(); l != NULL; l = l->next) { SPItem *item = SP_ITEM(l->data); @@ -2906,10 +2929,12 @@ void sp_selection_unset_mask(SPDesktop *desktop, bool apply_clip_path) { } /** - * Returns true if an undoable change should be recorded. + * \param with_margins margins defined in the xml under <sodipodi:namedview> + * "fit-margin-..." attributes. See SPDocument::fitToRect. + * \return true if an undoable change should be recorded. */ bool -fit_canvas_to_selection(SPDesktop *desktop) +fit_canvas_to_selection(SPDesktop *desktop, bool with_margins) { g_return_val_if_fail(desktop != NULL, false); SPDocument *doc = sp_desktop_document(desktop); @@ -2923,7 +2948,7 @@ fit_canvas_to_selection(SPDesktop *desktop) } Geom::OptRect const bbox(desktop->selection->bounds()); if (bbox) { - doc->fitToRect(*bbox); + doc->fitToRect(*bbox, with_margins); return true; } else { return false; @@ -2942,8 +2967,12 @@ verb_fit_canvas_to_selection(SPDesktop *const desktop) } } +/** + * \param with_margins margins defined in the xml under <sodipodi:namedview> + * "fit-margin-..." attributes. See SPDocument::fitToRect. + */ bool -fit_canvas_to_drawing(SPDocument *doc) +fit_canvas_to_drawing(SPDocument *doc, bool with_margins) { g_return_val_if_fail(doc != NULL, false); @@ -2951,7 +2980,7 @@ fit_canvas_to_drawing(SPDocument *doc) SPItem const *const root = SP_ITEM(doc->root); Geom::OptRect const bbox(root->getBounds(sp_item_i2d_affine(root))); if (bbox) { - doc->fitToRect(*bbox); + doc->fitToRect(*bbox, with_margins); return true; } else { return false; @@ -2967,6 +2996,11 @@ verb_fit_canvas_to_drawing(SPDesktop *desktop) } } +/** + * Fits canvas to selection or drawing with margins from <sodipodi:namedview> + * "fit-margin-..." attributes. See SPDocument::fitToRect and + * ui/dialog/page-sizer. + */ void fit_canvas_to_selection_or_drawing(SPDesktop *desktop) { g_return_if_fail(desktop != NULL); SPDocument *doc = sp_desktop_document(desktop); @@ -2975,8 +3009,8 @@ void fit_canvas_to_selection_or_drawing(SPDesktop *desktop) { g_return_if_fail(desktop->selection != NULL); bool const changed = ( desktop->selection->isEmpty() - ? fit_canvas_to_drawing(doc) - : fit_canvas_to_selection(desktop) ); + ? fit_canvas_to_drawing(doc, true) + : fit_canvas_to_selection(desktop, true) ); if (changed) { sp_document_done(sp_desktop_document(desktop), SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING, _("Fit Page to Selection or Drawing")); diff --git a/src/selection-chemistry.h b/src/selection-chemistry.h index 67e772a00..7cc5f8d9f 100644 --- a/src/selection-chemistry.h +++ b/src/selection-chemistry.h @@ -115,9 +115,9 @@ void sp_selection_create_bitmap_copy (SPDesktop *desktop); void sp_selection_set_mask(SPDesktop *desktop, bool apply_clip_path, bool apply_to_layer); void sp_selection_unset_mask(SPDesktop *desktop, bool apply_clip_path); -bool fit_canvas_to_selection(SPDesktop *); +bool fit_canvas_to_selection(SPDesktop *, bool with_margins = false); void verb_fit_canvas_to_selection(SPDesktop *); -bool fit_canvas_to_drawing(SPDocument *); +bool fit_canvas_to_drawing(SPDocument *, bool with_margins = false); void verb_fit_canvas_to_drawing(SPDesktop *); void fit_canvas_to_selection_or_drawing(SPDesktop *); diff --git a/src/selection.cpp b/src/selection.cpp index 4d92a18df..1e14591fa 100644 --- a/src/selection.cpp +++ b/src/selection.cpp @@ -162,24 +162,12 @@ void Selection::add(SPObject *obj, bool persist_selection_context/* = false */) _emitChanged(persist_selection_context); } -void Selection::add_box_perspective(SPBox3D *box) { - Persp3D *persp = box3d_get_perspective(box); - std::map<Persp3D *, unsigned int>::iterator p = _persps.find(persp); - if (p != _persps.end()) { - (*p).second++; - } else { - _persps[persp] = 1; - } -} - void Selection::add_3D_boxes_recursively(SPObject *obj) { std::list<SPBox3D *> boxes = box3d_extract_boxes(obj); for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) { SPBox3D *box = *i; - box3d_add_to_selection(box); _3dboxes.push_back(box); - add_box_perspective(box); } } @@ -220,33 +208,17 @@ void Selection::remove(SPObject *obj) { _emitChanged(); } -void Selection::remove_box_perspective(SPBox3D *box) { - Persp3D *persp = box3d_get_perspective(box); - std::map<Persp3D *, unsigned int>::iterator p = _persps.find(persp); - if (p == _persps.end()) { - g_print ("Warning! Trying to remove unselected perspective from selection!\n"); - return; - } - if ((*p).second > 1) { - _persps[persp]--; - } else { - _persps.erase(p); - } -} - void Selection::remove_3D_boxes_recursively(SPObject *obj) { std::list<SPBox3D *> boxes = box3d_extract_boxes(obj); for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) { SPBox3D *box = *i; - box3d_remove_from_selection(box); std::list<SPBox3D *>::iterator b = std::find(_3dboxes.begin(), _3dboxes.end(), box); if (b == _3dboxes.end()) { g_print ("Warning! Trying to remove unselected box from selection.\n"); return; } _3dboxes.erase(b); - remove_box_perspective(box); } } @@ -344,14 +316,27 @@ GSList const *Selection::reprList() { std::list<Persp3D *> const Selection::perspList() { std::list<Persp3D *> pl; - for (std::map<Persp3D *, unsigned int>::iterator p = _persps.begin(); p != _persps.end(); ++p) { - pl.push_back((*p).first); + for (std::list<SPBox3D *>::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) { + Persp3D *persp = box3d_get_perspective(*i); + if (std::find(pl.begin(), pl.end(), persp) == pl.end()) + pl.push_back(persp); } return pl; } -std::list<SPBox3D *> const Selection::box3DList() { - return _3dboxes; +std::list<SPBox3D *> const Selection::box3DList(Persp3D *persp) { + std::list<SPBox3D *> boxes; + if (persp) { + SPBox3D *box; + for (std::list<SPBox3D *>::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) { + box = *i; + if (persp == box3d_get_perspective(box)) + boxes.push_back(box); + } + } else { + boxes = _3dboxes; + } + return boxes; } SPObject *Selection::single() { @@ -441,48 +426,48 @@ boost::optional<Geom::Point> Selection::center() const { /** * Compute the list of points in the selection that are to be considered for snapping. */ -std::vector<std::pair<Geom::Point, int> > Selection::getSnapPoints(SnapPreferences const *snapprefs) const { +std::vector<Inkscape::SnapCandidatePoint> Selection::getSnapPoints(SnapPreferences const *snapprefs) const { GSList const *items = const_cast<Selection *>(this)->itemList(); SnapPreferences snapprefs_dummy = *snapprefs; // create a local copy of the snapping prefs snapprefs_dummy.setIncludeItemCenter(false); // locally disable snapping to the item center - std::vector<std::pair<Geom::Point, int> > p; + std::vector<Inkscape::SnapCandidatePoint> p; for (GSList const *iter = items; iter != NULL; iter = iter->next) { SPItem *this_item = SP_ITEM(iter->data); - sp_item_snappoints(this_item, false, p, &snapprefs_dummy); + sp_item_snappoints(this_item, p, &snapprefs_dummy); //Include the transformation origin for snapping //For a selection or group only the overall origin is considered if (snapprefs != NULL && snapprefs->getIncludeItemCenter()) { - p.push_back(std::make_pair(this_item->getCenter(), SNAPSOURCE_ROTATION_CENTER)); + p.push_back(Inkscape::SnapCandidatePoint(this_item->getCenter(), SNAPSOURCE_ROTATION_CENTER)); } } return p; } -std::vector<std::pair<Geom::Point, int> > Selection::getSnapPointsConvexHull(SnapPreferences const *snapprefs) const { +std::vector<Inkscape::SnapCandidatePoint> Selection::getSnapPointsConvexHull(SnapPreferences const *snapprefs) const { GSList const *items = const_cast<Selection *>(this)->itemList(); - std::vector<std::pair<Geom::Point, int> > p; + std::vector<Inkscape::SnapCandidatePoint> p; for (GSList const *iter = items; iter != NULL; iter = iter->next) { - sp_item_snappoints(SP_ITEM(iter->data), false, p, snapprefs); + sp_item_snappoints(SP_ITEM(iter->data), p, snapprefs); } - std::vector<std::pair<Geom::Point, int> > pHull; + std::vector<Inkscape::SnapCandidatePoint> pHull; if (!p.empty()) { - std::vector<std::pair<Geom::Point, int> >::iterator i; - Geom::RectHull cvh((p.front()).first); + std::vector<Inkscape::SnapCandidatePoint>::iterator i; + Geom::RectHull cvh((p.front()).getPoint()); for (i = p.begin(); i != p.end(); i++) { // these are the points we get back - cvh.add((*i).first); + cvh.add((*i).getPoint()); } Geom::OptRect rHull = cvh.bounds(); if (rHull) { for ( unsigned i = 0 ; i < 4 ; ++i ) { - pHull.push_back(std::make_pair(rHull->corner(i), SNAPSOURCE_CONVEX_HULL_CORNER)); + pHull.push_back(Inkscape::SnapCandidatePoint(rHull->corner(i), SNAPSOURCE_CONVEX_HULL_CORNER)); } } } diff --git a/src/selection.h b/src/selection.h index ecb1ef45e..b5a511e96 100644 --- a/src/selection.h +++ b/src/selection.h @@ -229,11 +229,14 @@ public: /// method for that GSList const *reprList(); - /* list of all perspectives which have a 3D box in the current selection + /** @brief Returns a list of all perspectives which have a 3D box in the current selection (these may also be nested in groups) */ std::list<Persp3D *> const perspList(); - std::list<SPBox3D *> const box3DList(); + /** @brief Returns a list of all 3D boxes in the current selection which are associated to @c + persp. If @c pers is @c NULL, return all selected boxes. + */ + std::list<SPBox3D *> const box3DList(Persp3D *persp = NULL); /** @brief Returns the number of layers in which there are selected objects */ guint numberOfLayers(); @@ -269,13 +272,13 @@ public: * @brief Gets the selection's snap points. * @return Selection's snap points */ - std::vector<std::pair<Geom::Point, int> > getSnapPoints(SnapPreferences const *snapprefs) const; + std::vector<Inkscape::SnapCandidatePoint> getSnapPoints(SnapPreferences const *snapprefs) const; /** * @brief Gets the snap points of a selection that form a convex hull. * @return Selection's convex hull points */ - std::vector<std::pair<Geom::Point, int> > getSnapPointsConvexHull(SnapPreferences const *snapprefs) const; + std::vector<Inkscape::SnapCandidatePoint> getSnapPointsConvexHull(SnapPreferences const *snapprefs) const; /** * @brief Connects a slot to be notified of selection changes @@ -351,7 +354,6 @@ private: void remove_box_perspective(SPBox3D *box); void remove_3D_boxes_recursively(SPObject *obj); - std::map<Persp3D *, unsigned int> _persps; std::list<SPBox3D *> _3dboxes; GC::soft_ptr<SPDesktop> _desktop; diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 8ff00d60a..5b129f8d8 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -295,7 +295,7 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s // but as a snap source we still need some nodes though! _snap_points.clear(); _snap_points = selection->getSnapPoints(&local_snapprefs); - std::vector<std::pair<Geom::Point, int> > snap_points_hull = selection->getSnapPointsConvexHull(&local_snapprefs); + std::vector<Inkscape::SnapCandidatePoint> snap_points_hull = selection->getSnapPointsConvexHull(&local_snapprefs); if (_snap_points.size() > 200) { /* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes An average user would rarely ever try to snap such a large number of nodes anyway, because @@ -313,11 +313,11 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s // any other special points Geom::Rect snap_points_bbox; if ( snap_points_hull.empty() == false ) { - std::vector<std::pair<Geom::Point, int> >::iterator i = snap_points_hull.begin(); - snap_points_bbox = Geom::Rect((*i).first, (*i).first); + std::vector<Inkscape::SnapCandidatePoint>::iterator i = snap_points_hull.begin(); + snap_points_bbox = Geom::Rect((*i).getPoint(), (*i).getPoint()); i++; while (i != snap_points_hull.end()) { - snap_points_bbox.expandTo((*i).first); + snap_points_bbox.expandTo((*i).getPoint()); i++; } } @@ -381,9 +381,9 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s } // Now let's reduce this to a single closest snappoint - Geom::Coord dsp = _snap_points.size() == 1 ? Geom::L2((_snap_points.at(0)).first - p) : NR_HUGE; - Geom::Coord dbbp = _bbox_points.size() == 1 ? Geom::L2((_bbox_points.at(0)).first - p) : NR_HUGE; - Geom::Coord dbbpft = _bbox_points_for_translating.size() == 1 ? Geom::L2((_bbox_points_for_translating.at(0)).first - p) : NR_HUGE; + Geom::Coord dsp = _snap_points.size() == 1 ? Geom::L2((_snap_points.at(0)).getPoint() - p) : NR_HUGE; + Geom::Coord dbbp = _bbox_points.size() == 1 ? Geom::L2((_bbox_points.at(0)).getPoint() - p) : NR_HUGE; + Geom::Coord dbbpft = _bbox_points_for_translating.size() == 1 ? Geom::L2((_bbox_points_for_translating.at(0)).getPoint() - p) : NR_HUGE; if (translating) { _bbox_points.clear(); @@ -1513,6 +1513,7 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) } } } + if (best_snapped_point.getSnapped()) { _desktop->snapindicator->set_new_snaptarget(best_snapped_point); } else { @@ -1643,15 +1644,15 @@ Geom::Point Inkscape::SelTrans::_calcAbsAffineGeom(Geom::Scale const geom_scale) return _calcAbsAffineDefault(geom_scale); // this is bogus, but we must return _something_ } -void Inkscape::SelTrans::_keepClosestPointOnly(std::vector<std::pair<Geom::Point, int> > &points, const Geom::Point &reference) +void Inkscape::SelTrans::_keepClosestPointOnly(std::vector<Inkscape::SnapCandidatePoint> &points, const Geom::Point &reference) { if (points.size() < 2) return; - std::pair<Geom::Point, int> closest_point = std::make_pair(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED); + Inkscape::SnapCandidatePoint closest_point = Inkscape::SnapCandidatePoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED); Geom::Coord closest_dist = NR_HUGE; - for(std::vector<std::pair<Geom::Point, int> >::const_iterator i = points.begin(); i != points.end(); i++) { - Geom::Coord dist = Geom::L2((*i).first - reference); + for(std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) { + Geom::Coord dist = Geom::L2((*i).getPoint() - reference); if (i == points.begin() || dist < closest_dist) { closest_point = *i; closest_dist = dist; diff --git a/src/seltrans.h b/src/seltrans.h index d0ac5dccf..8b2810621 100644 --- a/src/seltrans.h +++ b/src/seltrans.h @@ -102,7 +102,7 @@ private: Geom::Point _getGeomHandlePos(Geom::Point const &visual_handle_pos); Geom::Point _calcAbsAffineDefault(Geom::Scale const default_scale); Geom::Point _calcAbsAffineGeom(Geom::Scale const geom_scale); - void _keepClosestPointOnly(std::vector<std::pair<Geom::Point, int> > &points, const Geom::Point &reference); + void _keepClosestPointOnly(std::vector<Inkscape::SnapCandidatePoint> &points, const Geom::Point &reference); void _display_snapsource(); enum State { @@ -117,9 +117,9 @@ private: std::vector<Geom::Matrix> _items_affines; std::vector<Geom::Point> _items_centers; - std::vector<std::pair<Geom::Point, int> > _snap_points; - std::vector<std::pair<Geom::Point, int> > _bbox_points; // the bbox point of the selection as a whole, i.e. max. 4 corners plus optionally some midpoints - std::vector<std::pair<Geom::Point, int> > _bbox_points_for_translating; // the bbox points of each selected item, only to be used for translating + std::vector<Inkscape::SnapCandidatePoint> _snap_points; + std::vector<Inkscape::SnapCandidatePoint> _bbox_points; // the bbox point of the selection as a whole, i.e. max. 4 corners plus optionally some midpoints + std::vector<Inkscape::SnapCandidatePoint> _bbox_points_for_translating; // the bbox points of each selected item, only to be used for translating Inkscape::SelCue _selcue; diff --git a/src/snap-candidate.h b/src/snap-candidate.h new file mode 100644 index 000000000..13f1d069c --- /dev/null +++ b/src/snap-candidate.h @@ -0,0 +1,147 @@ +#ifndef SEEN_SNAP_CANDIDATE_H +#define SEEN_SNAP_CANDIDATE_H + +/** + * \file snap-candidate.h + * \brief some utility classes to store various kinds of snap candidates. + * + * Authors: + * Diederik van Lierop <mail@diedenrezi.nl> + * + * Copyright (C) 2010 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +//#include "snapped-point.h" +struct SPItem; // forward declaration + +namespace Inkscape { + +enum SnapTargetType { + SNAPTARGET_UNDEFINED = 0, + SNAPTARGET_GRID, + SNAPTARGET_GRID_INTERSECTION, + SNAPTARGET_GUIDE, + SNAPTARGET_GUIDE_INTERSECTION, + SNAPTARGET_GUIDE_ORIGIN, + SNAPTARGET_GRID_GUIDE_INTERSECTION, + SNAPTARGET_NODE_SMOOTH, + SNAPTARGET_NODE_CUSP, + SNAPTARGET_LINE_MIDPOINT, + SNAPTARGET_OBJECT_MIDPOINT, + SNAPTARGET_ROTATION_CENTER, + SNAPTARGET_HANDLE, + SNAPTARGET_PATH, + SNAPTARGET_PATH_INTERSECTION, + SNAPTARGET_BBOX_CORNER, + SNAPTARGET_BBOX_EDGE, + SNAPTARGET_BBOX_EDGE_MIDPOINT, + SNAPTARGET_BBOX_MIDPOINT, + SNAPTARGET_PAGE_BORDER, + SNAPTARGET_PAGE_CORNER, + SNAPTARGET_CONVEX_HULL_CORNER, + SNAPTARGET_ELLIPSE_QUADRANT_POINT, + SNAPTARGET_CENTER, // of ellipse + SNAPTARGET_CORNER, // of image or of rectangle + SNAPTARGET_TEXT_BASELINE, + SNAPTARGET_CONSTRAINED_ANGLE +}; + +enum SnapSourceType { + SNAPSOURCE_UNDEFINED = 0, + SNAPSOURCE_BBOX_CORNER, + SNAPSOURCE_BBOX_MIDPOINT, + SNAPSOURCE_BBOX_EDGE_MIDPOINT, + SNAPSOURCE_NODE_SMOOTH, + SNAPSOURCE_NODE_CUSP, + SNAPSOURCE_LINE_MIDPOINT, + SNAPSOURCE_OBJECT_MIDPOINT, + SNAPSOURCE_ROTATION_CENTER, + SNAPSOURCE_HANDLE, + SNAPSOURCE_PATH_INTERSECTION, + SNAPSOURCE_GUIDE, + SNAPSOURCE_GUIDE_ORIGIN, + SNAPSOURCE_CONVEX_HULL_CORNER, + SNAPSOURCE_ELLIPSE_QUADRANT_POINT, + SNAPSOURCE_CENTER, // of ellipse + SNAPSOURCE_CORNER, // of image or of rectangle + SNAPSOURCE_TEXT_BASELINE +}; + +/// Class to store data for points which are snap candidates, either as a source or as a target +class SnapCandidatePoint +{ +public: + SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source, long const source_num, Inkscape::SnapTargetType const target, Geom::Rect const &bbox) + : _point(point), _source_type(source), _target_type(target), _source_num(source_num), _target_bbox(bbox) {}; + + SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source, Inkscape::SnapTargetType const target) + : _point(point), _source_type(source), _target_type(target) + { + _source_num = 0; + _target_bbox = Geom::Rect(); + } + + SnapCandidatePoint(Geom::Point const &point, Inkscape::SnapSourceType const source, long const source_num = 0) + : _point(point), _source_type(source), _target_type(Inkscape::SNAPTARGET_UNDEFINED), _source_num(source_num) {_target_bbox = Geom::Rect();} + + inline Geom::Point const & getPoint() const {return _point;} + inline Inkscape::SnapSourceType getSourceType() const {return _source_type;} + inline Inkscape::SnapTargetType getTargetType() const {return _target_type;} + inline long getSourceNum() const {return _source_num;} + inline Geom::Rect const & getTargetBBox() const {return _target_bbox;} + +private: + // Coordinates of the point + Geom::Point _point; + + // If this SnapCandidatePoint is a snap source, then _source_type must be defined. If it + // is a snap target, then _target_type must be defined. If it's yet unknown whether it will + // be a source or target, then both may be defined + Inkscape::SnapSourceType _source_type; + Inkscape::SnapTargetType _target_type; + + //Sequence number of the source point within the set of points that is to be snapped. Starting at zero + long _source_num; + + // If this is a target and it belongs to a bounding box, e.g. when the target type is + // SNAPTARGET_BBOX_EDGE_MIDPOINT, then _target_bbox stores the relevant bounding box + Geom::Rect _target_bbox; +}; + +class SnapCandidateItem +{ +public: + SnapCandidateItem(SPItem* item, bool clip_or_mask, Geom::Matrix additional_affine) + : item(item), clip_or_mask(clip_or_mask), additional_affine(additional_affine) {} + ~SnapCandidateItem() {}; + + SPItem* item; // An item that is to be considered for snapping to + bool clip_or_mask; // If true, then item refers to a clipping path or a mask + + /* To find out the absolute position of a clipping path or mask, we not only need to know + * the transformation of the clipping path or mask itself, but also the transformation of + * the object to which the clip or mask is being applied; that transformation is stored here + */ + Geom::Matrix additional_affine; +} +; + +class SnapCandidatePath +{ + +public: + SnapCandidatePath(Geom::PathVector* path, SnapTargetType target, bool edited = false) + : path_vector(path), target_type(target), currently_being_edited(edited) {} + ~SnapCandidatePath() {}; + + Geom::PathVector* path_vector; + SnapTargetType target_type; + bool currently_being_edited; // true for the path that's currently being edited in the node tool (if any) + +}; + +} // end of namespace Inkscape + +#endif /* !SEEN_SNAP_CANDIDATE_H */ diff --git a/src/snap-preferences.cpp b/src/snap-preferences.cpp index 3e396a216..0ba3b15dc 100644 --- a/src/snap-preferences.cpp +++ b/src/snap-preferences.cpp @@ -17,7 +17,7 @@ Inkscape::SnapPreferences::PointType const Inkscape::SnapPreferences::SNAPPOINT_NODE = 0x1; Inkscape::SnapPreferences::PointType const Inkscape::SnapPreferences::SNAPPOINT_BBOX = 0x2; -Inkscape::SnapPreferences::PointType const Inkscape::SnapPreferences::SNAPPOINT_GUIDE = 0x4; +Inkscape::SnapPreferences::PointType const Inkscape::SnapPreferences::SNAPPOINT_OTHER = 0x4; Inkscape::SnapPreferences::SnapPreferences() : @@ -32,7 +32,7 @@ Inkscape::SnapPreferences::SnapPreferences() : _snap_to_page_border(false), _strict_snapping(true) { - setSnapFrom(SNAPPOINT_BBOX | SNAPPOINT_NODE | SNAPPOINT_GUIDE, true); //Snap any point. In v0.45 and earlier, this was controlled in the preferences tab + setSnapFrom(SNAPPOINT_BBOX | SNAPPOINT_NODE | SNAPPOINT_OTHER, true); //Snap any point. In v0.45 and earlier, this was controlled in the preferences tab } /* @@ -86,15 +86,15 @@ bool Inkscape::SnapPreferences::getSnapModeAny() const void Inkscape::SnapPreferences::setSnapModeGuide(bool enabled) { if (enabled) { - _snap_from |= Inkscape::SnapPreferences::SNAPPOINT_GUIDE; + _snap_from |= Inkscape::SnapPreferences::SNAPPOINT_OTHER; } else { - _snap_from &= ~Inkscape::SnapPreferences::SNAPPOINT_GUIDE; + _snap_from &= ~Inkscape::SnapPreferences::SNAPPOINT_OTHER; } } bool Inkscape::SnapPreferences::getSnapModeGuide() const { - return (_snap_from & Inkscape::SnapPreferences::SNAPPOINT_GUIDE); + return (_snap_from & Inkscape::SnapPreferences::SNAPPOINT_OTHER); } /** diff --git a/src/snap-preferences.h b/src/snap-preferences.h index 63d7fba15..c88503e7d 100644 --- a/src/snap-preferences.h +++ b/src/snap-preferences.h @@ -24,10 +24,10 @@ public: SnapPreferences(); /// Point types to snap. - typedef int PointType; - static const PointType SNAPPOINT_NODE; - static const PointType SNAPPOINT_BBOX; - static const PointType SNAPPOINT_GUIDE; + typedef int PointType; // can be only one of the types below, never two or more at the same time + static const PointType SNAPPOINT_NODE; // will in general not snap to bounding boxes + static const PointType SNAPPOINT_BBOX; // will in general not snap to nodes + static const PointType SNAPPOINT_OTHER;// e.g. guides, gradient knots void setSnapModeBBox(bool enabled); void setSnapModeNode(bool enabled); diff --git a/src/snap.cpp b/src/snap.cpp index 558f61814..c39cd9e04 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -13,7 +13,7 @@ * * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl> * Copyrigth (C) 2004 Nathan Hurst - * Copyright (C) 1999-2009 Authors + * Copyright (C) 1999-2010 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -159,24 +159,25 @@ bool SnapManager::gridSnapperMightSnap() const * because the original position should not be touched, then freeSnap() should be * called instead. * - * PS: SnapManager::setup() must have been called before calling this method, + * PS: + * 1) SnapManager::setup() must have been called before calling this method, * but only once for a set of points + * 2) Only to be used when a single source point is to be snapped; it assumes + * that source_num = 0, which is inefficient when snapping sets our source points * * \param point_type Category of points to which the source point belongs: node, guide or bounding box * \param p Current position of the snap source; will be overwritten by the position of the snap target if snapping has occurred * \param source_type Detailed description of the source type, will be used by the snap indicator - * \param first_point If true then this point is the first one from a set of points, all from the same selection and having the same transformation * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation */ void SnapManager::freeSnapReturnByRef(Inkscape::SnapPreferences::PointType point_type, Geom::Point &p, Inkscape::SnapSourceType const source_type, - bool first_point, Geom::OptRect const &bbox_to_snap) const { - //TODO: PointType and source_type are somewhat redundant; can't we get rid of the point_type parameter? - Inkscape::SnappedPoint const s = freeSnap(point_type, p, source_type, first_point, bbox_to_snap); + //TODO: SnapCandidatePoint and point_type are somewhat redundant; can't we get rid of the point_type parameter? + Inkscape::SnappedPoint const s = freeSnap(point_type, Inkscape::SnapCandidatePoint(p, source_type), bbox_to_snap); s.getPoint(p); } @@ -193,32 +194,28 @@ void SnapManager::freeSnapReturnByRef(Inkscape::SnapPreferences::PointType point * but only once for a set of points * * \param point_type Category of points to which the source point belongs: node, guide or bounding box - * \param p Current position of the snap source - * \param source_type Detailed description of the source type, will be used by the snap indicator - * \param first_point If true then this point is the first one from a set of points, all from the same selection and having the same transformation + * \param p Source point to be snapped * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics */ -Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointType point_type, - Geom::Point const &p, - Inkscape::SnapSourceType const &source_type, - bool first_point, +Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointType const point_type, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap) const { if (!someSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); + return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->freeSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, &_items_to_ignore, _unselected_nodes); + (*i)->freeSnap(sc, point_type, p, bbox_to_snap, &_items_to_ignore, _unselected_nodes); } - return findBestSnap(p, source_type, sc, false); + return findBestSnap(p, sc, false); } /** @@ -267,9 +264,9 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const Geom::Point const t_offset = t + grid->origin; SnappedConstraints sc; // Only the first three parameters are being used for grid snappers - snapper->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_NODE, t_offset, Inkscape::SNAPSOURCE_UNDEFINED, TRUE, Geom::OptRect(), NULL, NULL); + snapper->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_UNDEFINED),Geom::OptRect(), NULL, NULL); // Find the best snap for this grid, including intersections of the grid-lines - Inkscape::SnappedPoint s = findBestSnap(t_offset, Inkscape::SNAPSOURCE_UNDEFINED, sc, false); + Inkscape::SnappedPoint s = findBestSnap(Inkscape::SnapCandidatePoint(t_offset, Inkscape::SNAPSOURCE_UNDEFINED), sc, false); if (s.getSnapped() && (s.getSnapDistance() < nearest_distance)) { // use getSnapDistance() instead of getWeightedDistance() here because the pointer's position // doesn't tell us anything about which node to snap @@ -303,14 +300,17 @@ Geom::Point SnapManager::multipleOfGridPitch(Geom::Point const &t) const * because the original position should not be touched, then constrainedSnap() should * be called instead. * - * PS: SnapManager::setup() must have been called before calling this method, + * PS: + * 1) SnapManager::setup() must have been called before calling this method, * but only once for a set of points + * 2) Only to be used when a single source point is to be snapped; it assumes + * that source_num = 0, which is inefficient when snapping sets our source points + * * \param point_type Category of points to which the source point belongs: node, guide or bounding box * \param p Current position of the snap source; will be overwritten by the position of the snap target if snapping has occurred * \param source_type Detailed description of the source type, will be used by the snap indicator * \param constraint The direction or line along which snapping must occur - * \param first_point If true then this point is the first one from a set of points, all from the same selection and having the same transformation * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation */ @@ -318,10 +318,9 @@ void SnapManager::constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointTyp Geom::Point &p, Inkscape::SnapSourceType const source_type, Inkscape::Snapper::ConstraintLine const &constraint, - bool first_point, Geom::OptRect const &bbox_to_snap) const { - Inkscape::SnappedPoint const s = constrainedSnap(point_type, p, source_type, constraint, first_point, bbox_to_snap); + Inkscape::SnappedPoint const s = constrainedSnap(point_type, Inkscape::SnapCandidatePoint(p, source_type, 0), constraint, bbox_to_snap); s.getPoint(p); } @@ -337,35 +336,32 @@ void SnapManager::constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointTyp * but only once for a set of points * * \param point_type Category of points to which the source point belongs: node, guide or bounding box - * \param p Current position of the snap source - * \param source_type Detailed description of the source type, will be used by the snap indicator + * \param p Source point to be snapped * \param constraint The direction or line along which snapping must occur - * \param first_point If true then this point is the first one from a set of points, all from the same selection and having the same transformation * \param bbox_to_snap Bounding box hulling the set of points, all from the same selection and having the same transformation */ -Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::PointType point_type, - Geom::Point const &p, - Inkscape::SnapSourceType const &source_type, +Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::PointType const point_type, + Inkscape::SnapCandidatePoint const &p, Inkscape::Snapper::ConstraintLine const &constraint, - bool first_point, Geom::OptRect const &bbox_to_snap) const { if (!someSnapperMightSnap()) { - return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); + return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } // First project the mouse pointer onto the constraint - Geom::Point pp = constraint.projection(p); + Geom::Point pp = constraint.projection(p.getPoint()); // Then try to snap the projected point + Inkscape::SnapCandidatePoint candidate(pp, p.getSourceType(), p.getSourceNum(), Inkscape::SNAPTARGET_UNDEFINED, Geom::Rect()); SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, &_items_to_ignore); + (*i)->constrainedSnap(sc, point_type, candidate, bbox_to_snap, constraint, &_items_to_ignore); } - return findBestSnap(pp, source_type, sc, true); + return findBestSnap(candidate, sc, true); } /** @@ -390,9 +386,9 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, return; } - Inkscape::SnapSourceType source_type = Inkscape::SNAPSOURCE_GUIDE_ORIGIN; + Inkscape::SnapCandidatePoint candidate(p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN); if (drag_type == SP_DRAG_ROTATE) { - source_type = Inkscape::SNAPSOURCE_GUIDE; + candidate = Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_GUIDE); } // Snap to nodes @@ -404,12 +400,12 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, // Snap to guides & grid lines SnapperList snappers = getGridSnappers(); snappers.push_back(&guide); - for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, source_type, true, Geom::OptRect(), NULL, NULL); - } + for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + (*i)->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_OTHER, candidate, Geom::OptRect(), NULL, NULL); + } // Snap to intersections of curves, but not to the curves themselves! (see _snapTranslatingGuideToNodes in object-snapper.cpp) - Inkscape::SnappedPoint const s = findBestSnap(p, source_type, sc, false, true); + Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false, true); s.getPoint(p); } @@ -430,7 +426,7 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const { - if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) { + if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) { return; } @@ -438,23 +434,23 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) return; } - Inkscape::SnapSourceType source_type = Inkscape::SNAPSOURCE_GUIDE_ORIGIN; + Inkscape::SnapCandidatePoint candidate(p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, Inkscape::SNAPTARGET_UNDEFINED); // Snap to nodes or paths SnappedConstraints sc; Inkscape::Snapper::ConstraintLine cl(guideline.point_on_line, Geom::rot90(guideline.normal_to_line)); if (object.ThisSnapperMightSnap()) { - object.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, source_type, true, Geom::OptRect(), cl, NULL); + object.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_OTHER, candidate, Geom::OptRect(), cl, NULL); } // Snap to guides & grid lines - SnapperList snappers = getGridSnappers(); - snappers.push_back(&guide); - for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, source_type, true, Geom::OptRect(), cl, NULL); - } + SnapperList snappers = getGridSnappers(); + snappers.push_back(&guide); + for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { + (*i)->constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_OTHER, candidate, Geom::OptRect(), cl, NULL); + } - Inkscape::SnappedPoint const s = findBestSnap(p, source_type, sc, false); + Inkscape::SnappedPoint const s = findBestSnap(candidate, sc, false); s.getPoint(p); } @@ -486,7 +482,7 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) Inkscape::SnappedPoint SnapManager::_snapTransformed( Inkscape::SnapPreferences::PointType type, - std::vector<std::pair<Geom::Point, int> > const &points, + std::vector<Inkscape::SnapCandidatePoint> const &points, Geom::Point const &pointer, bool constrained, Inkscape::Snapper::ConstraintLine const &constraint, @@ -505,13 +501,14 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( ** Also used to globally disable all snapping */ if (someSnapperMightSnap() == false) { - return Inkscape::SnappedPoint(); + return Inkscape::SnappedPoint(pointer); } - std::vector<std::pair<Geom::Point, int> > transformed_points; + std::vector<Inkscape::SnapCandidatePoint> transformed_points; Geom::Rect bbox; - for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = points.begin(); i != points.end(); i++) { + long source_num = 0; + for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) { /* Work out the transformed version of this point */ Geom::Point transformed = _transformPoint(*i, transformation_type, transformation, origin, dim, uniform); @@ -523,7 +520,8 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( bbox.expandTo(transformed); } - transformed_points.push_back(std::make_pair(transformed, (*i).second)); + transformed_points.push_back(Inkscape::SnapCandidatePoint(transformed, (*i).getSourceType(), source_num)); + source_num++; } /* The current best transformation */ @@ -537,15 +535,16 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point g_assert(best_snapped_point.getAtIntersection() == false); - std::vector<std::pair<Geom::Point, int> >::const_iterator j = transformed_points.begin(); + std::vector<Inkscape::SnapCandidatePoint>::const_iterator j = transformed_points.begin(); + // std::cout << std::endl; - for (std::vector<std::pair<Geom::Point, int> >::const_iterator i = points.begin(); i != points.end(); i++) { + for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) { /* Snap it */ Inkscape::SnappedPoint snapped_point; Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; - Geom::Point const b = ((*i).first - origin); // vector to original point + Geom::Point const b = ((*i).getPoint() - origin); // vector to original point if (constrained) { if ((transformation_type == SCALE || transformation_type == STRETCH) && uniform) { @@ -554,18 +553,18 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( // calculate that line here dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, b); } else if (transformation_type == STRETCH) { // when non-uniform stretching { - dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i).first, component_vectors[dim]); + dedicated_constraint = Inkscape::Snapper::ConstraintLine((*i).getPoint(), component_vectors[dim]); } else if (transformation_type == TRANSLATION) { // When doing a constrained translation, all points will move in the same direction, i.e. // either horizontally or vertically. The lines along which they move are therefore all // parallel, but might not be colinear. Therefore we will have to set the point through // which the constraint-line runs here, for each point individually. - dedicated_constraint.setPoint((*i).first); + dedicated_constraint.setPoint((*i).getPoint()); } // else: leave the original constraint, e.g. for skewing if (transformation_type == SCALE && !uniform) { g_warning("Non-uniform constrained scaling is not supported!"); } - snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, i == points.begin(), bbox); + snapped_point = constrainedSnap(type, *j, dedicated_constraint, bbox); } else { bool const c1 = fabs(b[Geom::X]) < 1e-6; bool const c2 = fabs(b[Geom::Y]) < 1e-6; @@ -574,13 +573,13 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( // move in that specific direction; therefore it should only snap in that direction, otherwise // we will get snapped points with an invalid transformation dedicated_constraint = Inkscape::Snapper::ConstraintLine(origin, component_vectors[c1]); - snapped_point = constrainedSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), dedicated_constraint, i == points.begin(), bbox); + snapped_point = constrainedSnap(type, *j, dedicated_constraint, bbox); } else { - snapped_point = freeSnap(type, (*j).first, static_cast<Inkscape::SnapSourceType>((*j).second), i == points.begin(), bbox); + snapped_point = freeSnap(type, *j, bbox); } } // std::cout << "dist = " << snapped_point.getSnapDistance() << std::endl; - snapped_point.setPointerDistance(Geom::L2(pointer - (*i).first)); + snapped_point.setPointerDistance(Geom::L2(pointer - (*i).getPoint())); Geom::Point result; Geom::Point scale_metric(NR_HUGE, NR_HUGE); @@ -594,7 +593,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( switch (transformation_type) { case TRANSLATION: - result = snapped_point.getPoint() - (*i).first; + result = snapped_point.getPoint() - (*i).getPoint(); /* Consider the case in which a box is almost aligned with a grid in both * horizontal and vertical directions. The distance to the intersection of * the grid lines will always be larger then the distance to a single grid @@ -644,7 +643,7 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( snapped_point.setSecondSnapDistance(NR_HUGE); break; case SKEW: - result[0] = (snapped_point.getPoint()[dim] - ((*i).first)[dim]) / (((*i).first)[1 - dim] - origin[1 - dim]); // skew factor + result[0] = (snapped_point.getPoint()[dim] - ((*i).getPoint())[dim]) / (((*i).getPoint())[1 - dim] - origin[1 - dim]); // skew factor result[1] = transformation[1]; // scale factor // Store the metric for this transformation as a virtual distance snapped_point.setSnapDistance(std::abs(result[0] - transformation[0])); @@ -725,12 +724,13 @@ Inkscape::SnappedPoint SnapManager::_snapTransformed( */ Inkscape::SnappedPoint SnapManager::freeSnapTranslation(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Point const &tr) const { if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); @@ -748,13 +748,14 @@ Inkscape::SnappedPoint SnapManager::freeSnapTranslation(Inkscape::SnapPreference */ Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Inkscape::Snapper::ConstraintLine const &constraint, Geom::Point const &tr) const { if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, true, constraint, TRANSLATION, tr, Geom::Point(0,0), Geom::X, false); @@ -773,13 +774,14 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapTranslation(Inkscape::SnapPre */ Inkscape::SnappedPoint SnapManager::freeSnapScale(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Scale const &s, Geom::Point const &o) const { if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, false, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, false); @@ -798,14 +800,15 @@ Inkscape::SnappedPoint SnapManager::freeSnapScale(Inkscape::SnapPreferences::Poi */ Inkscape::SnappedPoint SnapManager::constrainedSnapScale(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Scale const &s, Geom::Point const &o) const { // When constrained scaling, only uniform scaling is supported. if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), SCALE, Geom::Point(s[Geom::X], s[Geom::Y]), o, Geom::X, true); @@ -825,7 +828,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapScale(Inkscape::SnapPreferenc */ Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Coord const &s, Geom::Point const &o, @@ -833,7 +836,8 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::SnapPrefere bool u) const { if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), STRETCH, Geom::Point(s, s), o, d, u), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), STRETCH, Geom::Point(s, s), o, d, u); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, true, Geom::Point(0,0), STRETCH, Geom::Point(s, s), o, d, u); @@ -853,7 +857,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapStretch(Inkscape::SnapPrefere */ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Inkscape::Snapper::ConstraintLine const &constraint, Geom::Point const &s, @@ -870,7 +874,8 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreference g_assert(!(point_type & Inkscape::SnapPreferences::SNAPPOINT_BBOX)); if (p.size() == 1) { - _displaySnapsource(point_type, std::make_pair(_transformPoint(p.at(0), SKEW, s, o, d, false), (p.at(0)).second)); + Geom::Point pt = _transformPoint(p.at(0), SKEW, s, o, d, false); + _displaySnapsource(point_type, Inkscape::SnapCandidatePoint(pt, p.at(0).getSourceType())); } return _snapTransformed(point_type, p, pointer, true, constraint, SKEW, s, o, d, false); @@ -880,19 +885,17 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreference * \brief Given a set of possible snap targets, find the best target (which is not necessarily * also the nearest target), and show the snap indicator if requested * - * \param p Current position of the snap source - * \param source_type Detailed description of the source type, will be used by the snap indicator + * \param p Source point to be snapped * \param sc A structure holding all snap targets that have been found so far * \param constrained True if the snap is constrained, e.g. for stretching or for purely horizontal translation. - * \param noCurves If true, then do consider snapping to intersections of curves, but not to the curves themself + * \param noCurves If true, then do consider snapping to intersections of curves, but not to the curves themselves * \return An instance of the SnappedPoint class, which holds data on the snap source, snap target, and various metrics */ -Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, - Inkscape::SnapSourceType const source_type, - SnappedConstraints &sc, - bool constrained, - bool noCurves) const +Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint const &p, + SnappedConstraints const &sc, + bool constrained, + bool noCurves) const { /* @@ -915,17 +918,17 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, // search for the closest snapped curve if (!noCurves) { - Inkscape::SnappedCurve closestCurve; - if (getClosestCurve(sc.curves, closestCurve)) { - sp_list.push_back(Inkscape::SnappedPoint(closestCurve)); - } + Inkscape::SnappedCurve closestCurve; + if (getClosestCurve(sc.curves, closestCurve)) { + sp_list.push_back(Inkscape::SnappedPoint(closestCurve)); + } } if (snapprefs.getSnapIntersectionCS()) { // search for the closest snapped intersection of curves Inkscape::SnappedPoint closestCurvesIntersection; - if (getClosestIntersectionCS(sc.curves, p, closestCurvesIntersection, _desktop->dt2doc())) { - closestCurvesIntersection.setSource(source_type); + if (getClosestIntersectionCS(sc.curves, p.getPoint(), closestCurvesIntersection, _desktop->dt2doc())) { + closestCurvesIntersection.setSource(p.getSourceType()); sp_list.push_back(closestCurvesIntersection); } } @@ -952,7 +955,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, // search for the closest snapped intersection of grid lines Inkscape::SnappedPoint closestGridPoint; if (getClosestIntersectionSL(sc.grid_lines, closestGridPoint)) { - closestGridPoint.setSource(source_type); + closestGridPoint.setSource(p.getSourceType()); closestGridPoint.setTarget(Inkscape::SNAPTARGET_GRID_INTERSECTION); sp_list.push_back(closestGridPoint); } @@ -960,7 +963,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, // search for the closest snapped intersection of guide lines Inkscape::SnappedPoint closestGuidePoint; if (getClosestIntersectionSL(sc.guide_lines, closestGuidePoint)) { - closestGuidePoint.setSource(source_type); + closestGuidePoint.setSource(p.getSourceType()); closestGuidePoint.setTarget(Inkscape::SNAPTARGET_GUIDE_INTERSECTION); sp_list.push_back(closestGuidePoint); } @@ -969,7 +972,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, if (snapprefs.getSnapIntersectionGG()) { Inkscape::SnappedPoint closestGridGuidePoint; if (getClosestIntersectionSL(sc.grid_lines, sc.guide_lines, closestGridGuidePoint)) { - closestGridGuidePoint.setSource(source_type); + closestGridGuidePoint.setSource(p.getSourceType()); closestGridGuidePoint.setTarget(Inkscape::SNAPTARGET_GRID_GUIDE_INTERSECTION); sp_list.push_back(closestGridGuidePoint); } @@ -977,11 +980,11 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, } // now let's see which snapped point gets a thumbs up - Inkscape::SnappedPoint bestSnappedPoint = Inkscape::SnappedPoint(p, Inkscape::SNAPSOURCE_UNDEFINED, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); + Inkscape::SnappedPoint bestSnappedPoint(p.getPoint()); // std::cout << "Finding the best snap..." << std::endl; for (std::list<Inkscape::SnappedPoint>::const_iterator i = sp_list.begin(); i != sp_list.end(); i++) { // first find out if this snapped point is within snapping range - // std::cout << "sp = " << from_2geom((*i).getPoint()); + // std::cout << "sp = " << (*i).getPoint() << " | source = " << (*i).getSource() << " | target = " << (*i).getTarget(); if ((*i).getSnapDistance() <= (*i).getTolerance()) { // if it's the first point, or if it is closer than the best snapped point so far if (i == sp_list.begin() || bestSnappedPoint.isOtherSnapBetter(*i, false)) { @@ -1009,7 +1012,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem const *item_to_ignore, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); @@ -1039,8 +1042,8 @@ void SnapManager::setup(SPDesktop const *desktop, void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, - std::vector<SPItem const *> const &items_to_ignore, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes, + std::vector<SPItem const *> &items_to_ignore, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); @@ -1054,7 +1057,7 @@ void SnapManager::setup(SPDesktop const *desktop, /// Setup, taking the list of items to ignore from the desktop's selection. void SnapManager::setupIgnoreSelection(SPDesktop const *desktop, bool snapindicator, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, SPGuide *guide_to_ignore) { _desktop = desktop; @@ -1087,7 +1090,7 @@ SPDocument *SnapManager::getDocument() const * \return The position of the point after transformation */ -Geom::Point SnapManager::_transformPoint(std::pair<Geom::Point, int> const &p, +Geom::Point SnapManager::_transformPoint(Inkscape::SnapCandidatePoint const &p, Transformation const transformation_type, Geom::Point const &transformation, Geom::Point const &origin, @@ -1098,10 +1101,10 @@ Geom::Point SnapManager::_transformPoint(std::pair<Geom::Point, int> const &p, Geom::Point transformed; switch (transformation_type) { case TRANSLATION: - transformed = p.first + transformation; + transformed = p.getPoint() + transformation; break; case SCALE: - transformed = (p.first - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin; + transformed = (p.getPoint() - origin) * Geom::Scale(transformation[Geom::X], transformation[Geom::Y]) + origin; break; case STRETCH: { @@ -1112,15 +1115,15 @@ Geom::Point SnapManager::_transformPoint(std::pair<Geom::Point, int> const &p, s[dim] = transformation[dim]; s[1 - dim] = 1; } - transformed = ((p.first - origin) * s) + origin; + transformed = ((p.getPoint() - origin) * s) + origin; break; } case SKEW: // Apply the skew factor - transformed[dim] = (p.first)[dim] + transformation[0] * ((p.first)[1 - dim] - origin[1 - dim]); + transformed[dim] = (p.getPoint())[dim] + transformation[0] * ((p.getPoint())[1 - dim] - origin[1 - dim]); // While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed. // Apply that scale factor here - transformed[1-dim] = (p.first - origin)[1 - dim] * transformation[1] + origin[1 - dim]; + transformed[1-dim] = (p.getPoint() - origin)[1 - dim] * transformation[1] + origin[1 - dim]; break; default: g_assert_not_reached(); @@ -1136,7 +1139,7 @@ Geom::Point SnapManager::_transformPoint(std::pair<Geom::Point, int> const &p, * \param p The transformed position of the source point, paired with an identifier of the type of the snap source. */ -void SnapManager::_displaySnapsource(Inkscape::SnapPreferences::PointType point_type, std::pair<Geom::Point, int> const &p) const { +void SnapManager::_displaySnapsource(Inkscape::SnapPreferences::PointType point_type, Inkscape::SnapCandidatePoint const &p) const { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/options/snapclosestonly/value")) { diff --git a/src/snap.h b/src/snap.h index 5696dcd53..ae136a355 100644 --- a/src/snap.h +++ b/src/snap.h @@ -10,7 +10,7 @@ * * Copyright (C) 2006-2007 Johan Engelen <johan@shouraizou.nl> * Copyright (C) 2000-2002 Lauris Kaplinski - * Copyright (C) 2000-2009 Authors + * Copyright (C) 2000-2010 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -66,14 +66,14 @@ class SPNamedView; class SnapManager { public: - enum Transformation { + enum Transformation { TRANSLATION, SCALE, STRETCH, SKEW }; - SnapManager(SPNamedView const *v); + SnapManager(SPNamedView const *v); typedef std::list<const Inkscape::Snapper*> SnapperList; @@ -81,34 +81,30 @@ public: bool gridSnapperMightSnap() const; void setup(SPDesktop const *desktop, - bool snapindicator = true, - SPItem const *item_to_ignore = NULL, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes = NULL, - SPGuide *guide_to_ignore = NULL); + bool snapindicator = true, + SPItem const *item_to_ignore = NULL, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, + SPGuide *guide_to_ignore = NULL); void setup(SPDesktop const *desktop, - bool snapindicator, - std::vector<SPItem const *> const &items_to_ignore, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes = NULL, - SPGuide *guide_to_ignore = NULL); + bool snapindicator, + std::vector<SPItem const *> &items_to_ignore, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, + SPGuide *guide_to_ignore = NULL); void setupIgnoreSelection(SPDesktop const *desktop, bool snapindicator = true, - std::vector<std::pair<Geom::Point, int> > *unselected_nodes = NULL, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, SPGuide *guide_to_ignore = NULL); // freeSnapReturnByRef() is preferred over freeSnap(), because it only returns a // point if snapping has occurred (by overwriting p); otherwise p is untouched void freeSnapReturnByRef(Inkscape::SnapPreferences::PointType point_type, - Geom::Point &p, - Inkscape::SnapSourceType const source_type, - bool first_point = true, - Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; + Geom::Point &p, + Inkscape::SnapSourceType const source_type, + Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; - - Inkscape::SnappedPoint freeSnap(Inkscape::SnapPreferences::PointType point_type, - Geom::Point const &p, - Inkscape::SnapSourceType const &source_type, - bool first_point = true, + Inkscape::SnappedPoint freeSnap(Inkscape::SnapPreferences::PointType const point_type, + Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap = Geom::OptRect() ) const; Geom::Point multipleOfGridPitch(Geom::Point const &t) const; @@ -116,47 +112,44 @@ public: // constrainedSnapReturnByRef() is preferred over constrainedSnap(), because it only returns a // point, by overwriting p, if snapping has occurred; otherwise p is untouched void constrainedSnapReturnByRef(Inkscape::SnapPreferences::PointType point_type, - Geom::Point &p, - Inkscape::SnapSourceType const source_type, - Inkscape::Snapper::ConstraintLine const &constraint, - bool first_point = true, - Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; - - Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapPreferences::PointType point_type, - Geom::Point const &p, - Inkscape::SnapSourceType const &source_type, - Inkscape::Snapper::ConstraintLine const &constraint, - bool first_point = true, + Geom::Point &p, + Inkscape::SnapSourceType const source_type, + Inkscape::Snapper::ConstraintLine const &constraint, + Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; + + Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapPreferences::PointType const point_type, + Inkscape::SnapCandidatePoint const &p, + Inkscape::Snapper::ConstraintLine const &constraint, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; void guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, SPGuideDragType drag_type) const; void guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const; Inkscape::SnappedPoint freeSnapTranslation(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Point const &tr) const; Inkscape::SnappedPoint constrainedSnapTranslation(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Inkscape::Snapper::ConstraintLine const &constraint, Geom::Point const &tr) const; Inkscape::SnappedPoint freeSnapScale(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Scale const &s, Geom::Point const &o) const; Inkscape::SnappedPoint constrainedSnapScale(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Scale const &s, Geom::Point const &o) const; Inkscape::SnappedPoint constrainedSnapStretch(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Geom::Coord const &s, Geom::Point const &o, @@ -164,7 +157,7 @@ public: bool uniform) const; Inkscape::SnappedPoint constrainedSnapSkew(Inkscape::SnapPreferences::PointType point_type, - std::vector<std::pair<Geom::Point, int> > const &p, + std::vector<Inkscape::SnapCandidatePoint> const &p, Geom::Point const &pointer, Inkscape::Snapper::ConstraintLine const &constraint, Geom::Point const &s, // s[0] = skew factor, s[1] = scale factor @@ -185,6 +178,8 @@ public: bool getSnapIndicator() const {return _snapindicator;} + Inkscape::SnappedPoint findBestSnap(Inkscape::SnapCandidatePoint const &p, SnappedConstraints const &sc, bool constrained, bool noCurves = false) const; + protected: SPNamedView const *_named_view; @@ -193,13 +188,13 @@ private: SPGuide *_guide_to_ignore; ///< A guide that should not be snapped to, e.g. the guide that is currently being dragged SPDesktop const *_desktop; bool _snapindicator; ///< When true, an indicator will be drawn at the position that was being snapped to - std::vector<std::pair<Geom::Point, int> > *_unselected_nodes; ///< Nodes of the path that is currently being edited and which have not been selected and which will therefore be stationary. Only these nodes will be considered for snapping to. Of each unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored + std::vector<Inkscape::SnapCandidatePoint> *_unselected_nodes; ///< Nodes of the path that is currently being edited and which have not been selected and which will therefore be stationary. Only these nodes will be considered for snapping to. Of each unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored //TODO: Make _unselected_nodes type safe; in the line above int is used for Inkscape::SnapTargetType, but if I remember //correctly then in other cases the int is being used for Inkscape::SnapSourceType, or for both. How to make //this type safe? Inkscape::SnappedPoint _snapTransformed(Inkscape::SnapPreferences::PointType type, - std::vector<std::pair<Geom::Point, int> > const &points, + std::vector<Inkscape::SnapCandidatePoint> const &points, Geom::Point const &pointer, bool constrained, Inkscape::Snapper::ConstraintLine const &constraint, @@ -209,16 +204,14 @@ private: Geom::Dim2 dim, bool uniform) const; - Geom::Point _transformPoint(std::pair<Geom::Point, int> const &p, + Geom::Point _transformPoint(Inkscape::SnapCandidatePoint const &p, Transformation const transformation_type, Geom::Point const &transformation, Geom::Point const &origin, Geom::Dim2 const dim, bool const uniform) const; - void _displaySnapsource(Inkscape::SnapPreferences::PointType point_type, std::pair<Geom::Point, int> const &p) const; - - Inkscape::SnappedPoint findBestSnap(Geom::Point const &p, Inkscape::SnapSourceType const source_type, SnappedConstraints &sc, bool constrained, bool noCurves = false) const; + void _displaySnapsource(Inkscape::SnapPreferences::PointType point_type, Inkscape::SnapCandidatePoint const &p) const; }; #endif /* !SEEN_SNAP_H */ diff --git a/src/snapped-curve.cpp b/src/snapped-curve.cpp index 4da2d4c7d..334038638 100644 --- a/src/snapped-curve.cpp +++ b/src/snapped-curve.cpp @@ -12,7 +12,7 @@ #include <2geom/crossing.h> #include <2geom/path-intersection.h> -Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve, SnapSourceType source, SnapTargetType target) +Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve, SnapSourceType source, long source_num, SnapTargetType target) { _distance = snapped_distance; _tolerance = std::max(snapped_tolerance, 1.0); @@ -25,6 +25,7 @@ Inkscape::SnappedCurve::SnappedCurve(Geom::Point const &snapped_point, Geom::Coo _at_intersection = false; _fully_constrained = fully_constrained; _source = source; + _source_num = source_num; _target = target; } @@ -41,6 +42,7 @@ Inkscape::SnappedCurve::SnappedCurve() _at_intersection = false; _fully_constrained = false; _source = SNAPSOURCE_UNDEFINED; + _source_num = 0; _target = SNAPTARGET_UNDEFINED; } @@ -83,12 +85,12 @@ Inkscape::SnappedPoint Inkscape::SnappedCurve::intersect(SnappedCurve const &cur // TODO: Investigate whether it is possible to use document coordinates everywhere // in the snapper code. Only the mouse position should be in desktop coordinates, I guess. // All paths are already in document coords and we are certainly not going to change THAT. - return SnappedPoint(best_p, Inkscape::SNAPSOURCE_UNDEFINED, Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryDist, primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, true, + return SnappedPoint(best_p, Inkscape::SNAPSOURCE_UNDEFINED, primaryC->getSourceNum(), Inkscape::SNAPTARGET_PATH_INTERSECTION, primaryDist, primaryC->getTolerance(), primaryC->getAlwaysSnap(), true, true, secondaryDist, secondaryC->getTolerance(), secondaryC->getAlwaysSnap()); } // No intersection - return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); + return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, 0, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); } // search for the closest snapped line diff --git a/src/snapped-curve.h b/src/snapped-curve.h index 0a1fe2431..4eea6e734 100644 --- a/src/snapped-curve.h +++ b/src/snapped-curve.h @@ -24,7 +24,7 @@ class SnappedCurve : public SnappedPoint { public: SnappedCurve(); - SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve, SnapSourceType source, SnapTargetType target); + SnappedCurve(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, Geom::Coord const &snapped_tolerance, bool const &always_snap, bool const &fully_constrained, Geom::Curve const *curve, SnapSourceType source, long source_num, SnapTargetType target); ~SnappedCurve(); Inkscape::SnappedPoint intersect(SnappedCurve const &curve, Geom::Point const &p, Geom::Matrix dt2doc) const; //intersect with another SnappedCurve diff --git a/src/snapped-line.cpp b/src/snapped-line.cpp index 3ebbeaf70..9dde22a4e 100644 --- a/src/snapped-line.cpp +++ b/src/snapped-line.cpp @@ -11,12 +11,13 @@ #include "snapped-line.h" #include <2geom/line.h> -Inkscape::SnappedLineSegment::SnappedLineSegment(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &start_point_of_line, Geom::Point const &end_point_of_line) +Inkscape::SnappedLineSegment::SnappedLineSegment(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &start_point_of_line, Geom::Point const &end_point_of_line) : _start_point_of_line(start_point_of_line), _end_point_of_line(end_point_of_line) { _point = snapped_point; _source = source; - _target = target; + _source_num = source_num; + _target = target; _distance = snapped_distance; _tolerance = std::max(snapped_tolerance, 1.0); _always_snap = always_snap; @@ -32,7 +33,8 @@ Inkscape::SnappedLineSegment::SnappedLineSegment() _end_point_of_line = Geom::Point(0,0); _point = Geom::Point(0,0); _source = SNAPSOURCE_UNDEFINED; - _target = SNAPTARGET_UNDEFINED; + _source_num = 0; + _target = SNAPTARGET_UNDEFINED; _distance = NR_HUGE; _tolerance = 1; _always_snap = false; @@ -50,19 +52,19 @@ Inkscape::SnappedLineSegment::~SnappedLineSegment() Inkscape::SnappedPoint Inkscape::SnappedLineSegment::intersect(SnappedLineSegment const &line) const { Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default - try - { - inters = Geom::intersection(getLineSegment(), line.getLineSegment()); - } - catch (Geom::InfiniteSolutions e) - { - // We're probably dealing with parallel lines, so they don't really cross - inters = Geom::OptCrossing(); - } + try + { + inters = Geom::intersection(getLineSegment(), line.getLineSegment()); + } + catch (Geom::InfiniteSolutions e) + { + // We're probably dealing with parallel lines, so they don't really cross + inters = Geom::OptCrossing(); + } if (inters) { Geom::Point inters_pt = getLineSegment().pointAt((*inters).ta); - /* If a snapper has been told to "always snap", then this one should be preferred + /* If a snapper has been told to "always snap", then this one should be preferred * over the other, if that other one has not been told so. (The preferred snapper * will be labeled "primary" below) */ @@ -78,22 +80,23 @@ Inkscape::SnappedPoint Inkscape::SnappedLineSegment::intersect(SnappedLineSegmen Inkscape::SnappedLineSegment const *secondarySLS = use_this_as_primary ? &line : this; Geom::Coord primaryDist = use_this_as_primary ? Geom::L2(inters_pt - this->getPoint()) : Geom::L2(inters_pt - line.getPoint()); Geom::Coord secondaryDist = use_this_as_primary ? Geom::L2(inters_pt - line.getPoint()) : Geom::L2(inters_pt - this->getPoint()); - return SnappedPoint(inters_pt, SNAPSOURCE_UNDEFINED, SNAPTARGET_PATH_INTERSECTION, primaryDist, primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, true, + return SnappedPoint(inters_pt, SNAPSOURCE_UNDEFINED, primarySLS->getSourceNum(), SNAPTARGET_PATH_INTERSECTION, primaryDist, primarySLS->getTolerance(), primarySLS->getAlwaysSnap(), true, true, secondaryDist, secondarySLS->getTolerance(), secondarySLS->getAlwaysSnap()); } // No intersection - return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); + return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, 0, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); }; -Inkscape::SnappedLine::SnappedLine(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) +Inkscape::SnappedLine::SnappedLine(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) : _normal_to_line(normal_to_line), _point_on_line(point_on_line) { - _source = source; - _target = target; - _distance = snapped_distance; + _source = source; + _source_num = source_num; + _target = target; + _distance = snapped_distance; _tolerance = std::max(snapped_tolerance, 1.0); _always_snap = always_snap; _second_distance = NR_HUGE; @@ -108,7 +111,8 @@ Inkscape::SnappedLine::SnappedLine() _normal_to_line = Geom::Point(0,0); _point_on_line = Geom::Point(0,0); _source = SNAPSOURCE_UNDEFINED; - _target = SNAPTARGET_UNDEFINED; + _source_num = 0; + _target = SNAPTARGET_UNDEFINED; _distance = NR_HUGE; _tolerance = 1; _always_snap = false; @@ -130,19 +134,19 @@ Inkscape::SnappedPoint Inkscape::SnappedLine::intersect(SnappedLine const &line) // The point of intersection should be considered for snapping, but might be outside the snapping range Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default - try - { - inters = Geom::intersection(getLine(), line.getLine()); - } - catch (Geom::InfiniteSolutions e) - { - // We're probably dealing with parallel lines, so they don't really cross - inters = Geom::OptCrossing(); - } + try + { + inters = Geom::intersection(getLine(), line.getLine()); + } + catch (Geom::InfiniteSolutions e) + { + // We're probably dealing with parallel lines, so they don't really cross + inters = Geom::OptCrossing(); + } if (inters) { - Geom::Point inters_pt = getLine().pointAt((*inters).ta); - /* If a snapper has been told to "always snap", then this one should be preferred + Geom::Point inters_pt = getLine().pointAt((*inters).ta); + /* If a snapper has been told to "always snap", then this one should be preferred * over the other, if that other one has not been told so. (The preferred snapper * will be labelled "primary" below) */ @@ -157,14 +161,14 @@ Inkscape::SnappedPoint Inkscape::SnappedLine::intersect(SnappedLine const &line) Inkscape::SnappedLine const *secondarySL = use_this_as_primary ? &line : this; Geom::Coord primaryDist = use_this_as_primary ? Geom::L2(inters_pt - this->getPoint()) : Geom::L2(inters_pt - line.getPoint()); Geom::Coord secondaryDist = use_this_as_primary ? Geom::L2(inters_pt - line.getPoint()) : Geom::L2(inters_pt - this->getPoint()); - return SnappedPoint(inters_pt, Inkscape::SNAPSOURCE_UNDEFINED, Inkscape::SNAPTARGET_UNDEFINED, primaryDist, primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, true, + return SnappedPoint(inters_pt, Inkscape::SNAPSOURCE_UNDEFINED, primarySL->getSourceNum(), Inkscape::SNAPTARGET_UNDEFINED, primaryDist, primarySL->getTolerance(), primarySL->getAlwaysSnap(), true, true, secondaryDist, secondarySL->getTolerance(), secondarySL->getAlwaysSnap()); // The type of the snap target is yet undefined, as we cannot tell whether // we're snapping to grid or the guide lines; must be set by on a higher level } // No intersection - return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); + return SnappedPoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, 0, SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false, false, NR_HUGE, 0, false); } // search for the closest snapped line segment diff --git a/src/snapped-line.h b/src/snapped-line.h index 3dec432e7..b95e7a7df 100644 --- a/src/snapped-line.h +++ b/src/snapped-line.h @@ -23,7 +23,7 @@ class SnappedLineSegment : public SnappedPoint { public: SnappedLineSegment(); - SnappedLineSegment(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &snapped_tolerance,bool const &always_snap, Geom::Point const &start_point_of_line, Geom::Point const &end_point_of_line); + SnappedLineSegment(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &snapped_tolerance,bool const &always_snap, Geom::Point const &start_point_of_line, Geom::Point const &end_point_of_line); ~SnappedLineSegment(); Inkscape::SnappedPoint intersect(SnappedLineSegment const &line) const; //intersect with another SnappedLineSegment Geom::LineSegment getLineSegment() const {return Geom::LineSegment(_start_point_of_line, _end_point_of_line);} @@ -39,7 +39,7 @@ class SnappedLine : public SnappedPoint { public: SnappedLine(); - SnappedLine(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &normal_to_line, Geom::Point const &point_on_line); + SnappedLine(Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &snapped_tolerance, bool const &always_snap, Geom::Point const &normal_to_line, Geom::Point const &point_on_line); ~SnappedLine(); Inkscape::SnappedPoint intersect(SnappedLine const &line) const; //intersect with another SnappedLine // This line is described by this equation: diff --git a/src/snapped-point.cpp b/src/snapped-point.cpp index 2d028c6d7..e3559d655 100644 --- a/src/snapped-point.cpp +++ b/src/snapped-point.cpp @@ -14,8 +14,8 @@ #include "preferences.h" // overloaded constructor -Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained) - : _point(p), _source(source), _target(target), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a) +Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained) + : _point(p), _source(source), _source_num(source_num), _target(target), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a) { // tolerance should never be smaller than 1 px, as it is used for normalization in isOtherSnapBetter. We don't want a division by zero. _at_intersection = false; @@ -25,22 +25,61 @@ Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapSourceType const _second_always_snap = false; _transformation = Geom::Point(1,1); _pointer_distance = NR_HUGE; + _target_bbox = Geom::Rect(); } -Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2) - : _point(p), _source(source), _target(target), _at_intersection(at_intersection), _fully_constrained(fully_constrained), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a), +Inkscape::SnappedPoint::SnappedPoint(Inkscape::SnapCandidatePoint const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained) + : _target(target), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a) +{ + _point = p.getPoint(); + _source = p.getSourceType(); + _source_num = p.getSourceNum(); + _at_intersection = false; + _fully_constrained = fully_constrained; + _second_distance = NR_HUGE; + _second_tolerance = 1; + _second_always_snap = false; + _transformation = Geom::Point(1,1); + _pointer_distance = NR_HUGE; + _target_bbox = Geom::Rect(); + +} + +Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2) + : _point(p), _source(source), _source_num(source_num), _target(target), _at_intersection(at_intersection), _fully_constrained(fully_constrained), _distance(d), _tolerance(std::max(t,1.0)), _always_snap(a), _second_distance(d2), _second_tolerance(std::max(t2,1.0)), _second_always_snap(a2) { // tolerance should never be smaller than 1 px, as it is used for normalization in // isOtherSnapBetter. We don't want a division by zero. _transformation = Geom::Point(1,1); _pointer_distance = NR_HUGE; + _target_bbox = Geom::Rect(); } Inkscape::SnappedPoint::SnappedPoint() { _point = Geom::Point(0,0); _source = SNAPSOURCE_UNDEFINED, + _source_num = 0, + _target = SNAPTARGET_UNDEFINED, + _at_intersection = false; + _fully_constrained = false; + _distance = NR_HUGE; + _tolerance = 1; + _always_snap = false; + _second_distance = NR_HUGE; + _second_tolerance = 1; + _second_always_snap = false; + _transformation = Geom::Point(1,1); + _pointer_distance = NR_HUGE; + _target_bbox = Geom::Rect(); +} + +Inkscape::SnappedPoint::SnappedPoint(Geom::Point const &p) +{ + _point = p; + _source = SNAPSOURCE_UNDEFINED, + _source_num = 0, _target = SNAPTARGET_UNDEFINED, _at_intersection = false; _fully_constrained = false; @@ -52,6 +91,7 @@ Inkscape::SnappedPoint::SnappedPoint() _second_always_snap = false; _transformation = Geom::Point(1,1); _pointer_distance = NR_HUGE; + _target_bbox = Geom::Rect(); } Inkscape::SnappedPoint::~SnappedPoint() @@ -68,12 +108,12 @@ void Inkscape::SnappedPoint::getPoint(Geom::Point &p) const } // search for the closest snapped point -bool getClosestSP(std::list<Inkscape::SnappedPoint> &list, Inkscape::SnappedPoint &result) +bool getClosestSP(std::list<Inkscape::SnappedPoint> const &list, Inkscape::SnappedPoint &result) { bool success = false; for (std::list<Inkscape::SnappedPoint>::const_iterator i = list.begin(); i != list.end(); i++) { - if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { + if ((i == list.begin()) || (*i).getSnapDistance() < result.getSnapDistance()) { result = *i; success = true; } @@ -85,6 +125,10 @@ bool getClosestSP(std::list<Inkscape::SnappedPoint> &list, Inkscape::SnappedPoin bool Inkscape::SnappedPoint::isOtherSnapBetter(Inkscape::SnappedPoint const &other_one, bool weighted) const { + if (!other_one.getSnapped()) { + return false; + } + double dist_other = other_one.getSnapDistance(); double dist_this = getSnapDistance(); @@ -93,9 +137,9 @@ bool Inkscape::SnappedPoint::isOtherSnapBetter(Inkscape::SnappedPoint const &oth // (both the snap distance and the pointer distance are measured in document pixels, not in screen pixels) if (weighted) { - Geom::Coord const dist_pointer_other = other_one.getPointerDistance(); - Geom::Coord const dist_pointer_this = getPointerDistance(); - // Weight factor: controls which node should be preferred for snapping, which is either + Geom::Coord const dist_pointer_other = other_one.getPointerDistance(); + Geom::Coord const dist_pointer_this = getPointerDistance(); + // Weight factor: controls which node should be preferred for snapping, which is either // the node with the closest snap (w = 0), or the node closest to the mousepointer (w = 1) Inkscape::Preferences *prefs = Inkscape::Preferences::get(); double w = prefs->getDoubleLimited("/options/snapweight/value", 0.5, 0, 1); @@ -103,21 +147,21 @@ bool Inkscape::SnappedPoint::isOtherSnapBetter(Inkscape::SnappedPoint const &oth w = 1; } if (w > 0) { - if (!(w == 1 && dist_pointer_this == dist_pointer_other)) { - // When accounting for the distance to the mouse pointer, then at least one of the snapped points should - // have that distance set. If not, then this is a bug. Either "weighted" must be set to false, or the - // mouse pointer distance must be set. - g_assert(dist_pointer_this != NR_HUGE || dist_pointer_other != NR_HUGE); - // The snap distance will always be smaller than the tolerance set for the snapper. The pointer distance can - // however be very large. To compare these in a fair way, we will have to normalize these metrics first - // The closest pointer distance will be normalized to 1.0; the other one will be > 1.0 - // The snap distance will be normalized to 1.0 if it's equal to the snapper tolerance - double const norm_p = std::min(dist_pointer_this, dist_pointer_other); - double const norm_t_other = std::min(50.0, other_one.getTolerance()); - double const norm_t_this = std::min(50.0, getTolerance()); - dist_other = w * dist_pointer_other / norm_p + (1-w) * dist_other / norm_t_other; - dist_this = w * dist_pointer_this / norm_p + (1-w) * dist_this / norm_t_this; - } + if (!(w == 1 && dist_pointer_this == dist_pointer_other)) { + // When accounting for the distance to the mouse pointer, then at least one of the snapped points should + // have that distance set. If not, then this is a bug. Either "weighted" must be set to false, or the + // mouse pointer distance must be set. + g_assert(dist_pointer_this != NR_HUGE || dist_pointer_other != NR_HUGE); + // The snap distance will always be smaller than the tolerance set for the snapper. The pointer distance can + // however be very large. To compare these in a fair way, we will have to normalize these metrics first + // The closest pointer distance will be normalized to 1.0; the other one will be > 1.0 + // The snap distance will be normalized to 1.0 if it's equal to the snapper tolerance + double const norm_p = std::min(dist_pointer_this, dist_pointer_other); + double const norm_t_other = std::min(50.0, other_one.getTolerance()); + double const norm_t_this = std::min(50.0, getTolerance()); + dist_other = w * dist_pointer_other / norm_p + (1-w) * dist_other / norm_t_other; + dist_this = w * dist_pointer_this / norm_p + (1-w) * dist_this / norm_t_this; + } } } diff --git a/src/snapped-point.h b/src/snapped-point.h index 70d16b0be..1497802c0 100644 --- a/src/snapped-point.h +++ b/src/snapped-point.h @@ -16,70 +16,21 @@ #include <list> #include <libnr/nr-values.h> //Because of NR_HUGE #include <2geom/geom.h> +#include <snap-candidate.h> namespace Inkscape { -enum SnapTargetType { - SNAPTARGET_UNDEFINED = 0, - SNAPTARGET_GRID, - SNAPTARGET_GRID_INTERSECTION, - SNAPTARGET_GUIDE, - SNAPTARGET_GUIDE_INTERSECTION, - SNAPTARGET_GUIDE_ORIGIN, - SNAPTARGET_GRID_GUIDE_INTERSECTION, - SNAPTARGET_NODE_SMOOTH, - SNAPTARGET_NODE_CUSP, - SNAPTARGET_LINE_MIDPOINT, - SNAPTARGET_OBJECT_MIDPOINT, - SNAPTARGET_ROTATION_CENTER, - SNAPTARGET_HANDLE, - SNAPTARGET_PATH, - SNAPTARGET_PATH_INTERSECTION, - SNAPTARGET_BBOX_CORNER, - SNAPTARGET_BBOX_EDGE, - SNAPTARGET_BBOX_EDGE_MIDPOINT, - SNAPTARGET_BBOX_MIDPOINT, - SNAPTARGET_GRADIENTS_PARENT_BBOX, - SNAPTARGET_PAGE_BORDER, - SNAPTARGET_PAGE_CORNER, - SNAPTARGET_CONVEX_HULL_CORNER, - SNAPTARGET_ELLIPSE_QUADRANT_POINT, - SNAPTARGET_CENTER, // of ellipse - SNAPTARGET_CORNER, // of image or of rectangle - SNAPTARGET_TEXT_BASELINE -}; - -enum SnapSourceType { - SNAPSOURCE_UNDEFINED = 0, - SNAPSOURCE_BBOX_CORNER, - SNAPSOURCE_BBOX_MIDPOINT, - SNAPSOURCE_BBOX_EDGE_MIDPOINT, - SNAPSOURCE_NODE_SMOOTH, - SNAPSOURCE_NODE_CUSP, - SNAPSOURCE_LINE_MIDPOINT, - SNAPSOURCE_OBJECT_MIDPOINT, - SNAPSOURCE_ROTATION_CENTER, - SNAPSOURCE_HANDLE, - SNAPSOURCE_PATH_INTERSECTION, - SNAPSOURCE_GUIDE, - SNAPSOURCE_GUIDE_ORIGIN, - SNAPSOURCE_CONVEX_HULL_CORNER, - SNAPSOURCE_ELLIPSE_QUADRANT_POINT, - SNAPSOURCE_CENTER, // of ellipse - SNAPSOURCE_CORNER, // of image or of rectangle - SNAPSOURCE_TEXT_BASELINE -}; - - /// Class describing the result of an attempt to snap. class SnappedPoint { public: SnappedPoint(); - SnappedPoint(Geom::Point const &p, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2); - SnappedPoint(Geom::Point const &p, SnapSourceType const &source, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained); + SnappedPoint(Geom::Point const &p); + SnappedPoint(Geom::Point const &p, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &at_intersection, bool const &fully_constrained, Geom::Coord const &d2, Geom::Coord const &t2, bool const &a2); + SnappedPoint(Geom::Point const &p, SnapSourceType const &source, long source_num, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained); + SnappedPoint(SnapCandidatePoint const &p, SnapTargetType const &target, Geom::Coord const &d, Geom::Coord const &t, bool const &a, bool const &fully_constrained); ~SnappedPoint(); Geom::Coord getSnapDistance() const {return _distance;} @@ -114,14 +65,18 @@ public: void setTransformation(Geom::Point const t) {_transformation = t;} void setTarget(SnapTargetType const target) {_target = target;} SnapTargetType getTarget() const {return _target;} + void setTargetBBox(Geom::Rect const target) {_target_bbox = target;} + Geom::Rect & getTargetBBox() {return _target_bbox;} void setSource(SnapSourceType const source) {_source = source;} - SnapSourceType getSource() const {return _source;} + SnapSourceType getSource() const {return _source;} + long getSourceNum() const {return _source_num;} bool isOtherSnapBetter(SnappedPoint const &other_one, bool weighted) const; /*void dump() const { std::cout << "_point = " << _point << std::endl; std::cout << "_source = " << _source << std::endl; + std::cout << "_source_num = " << _source_num << std::endl; std::cout << "_target = " << _target << std::endl; std::cout << "_at_intersection = " << _at_intersection << std::endl; std::cout << "_fully_constrained = " << _fully_constrained << std::endl; @@ -138,6 +93,7 @@ public: protected: Geom::Point _point; // Location of the snapped point SnapSourceType _source; // Describes what snapped + long _source_num; // Sequence number of the source point that snapped, if that point is part of a set of points. (starting at zero) SnapTargetType _target; // Describes to what we've snapped to bool _at_intersection; // If true, the snapped point is at an intersection bool _fully_constrained; // When snapping for example to a node, then the snap will be "fully constrained". @@ -153,7 +109,7 @@ protected: bool _always_snap; /* If the snapped point is at an intersection of e.g. two lines, then this is - the distance to the fartest line */ + the distance to the farthest line */ Geom::Coord _second_distance; /* The snapping tolerance in screen pixels (depends on zoom)*/ Geom::Coord _second_tolerance; @@ -161,13 +117,15 @@ protected: bool _second_always_snap; /* The transformation (translation, scale, skew, or stretch) from the original point to the snapped point */ Geom::Point _transformation; + /* The bounding box we've snapped to (when applicable); will be used by the snapindicator */ + Geom::Rect _target_bbox; /* Distance from the un-transformed point to the mouse pointer, measured at the point in time when dragging started */ Geom::Coord _pointer_distance; }; }// end of namespace Inkscape -bool getClosestSP(std::list<Inkscape::SnappedPoint> &list, Inkscape::SnappedPoint &result); +bool getClosestSP(std::list<Inkscape::SnappedPoint> const &list, Inkscape::SnappedPoint &result); #endif /* !SEEN_SNAPPEDPOINT_H */ diff --git a/src/snapper.cpp b/src/snapper.cpp index 751b663e3..fb7281c30 100644 --- a/src/snapper.cpp +++ b/src/snapper.cpp @@ -19,9 +19,9 @@ * \param d Snap tolerance. */ Inkscape::Snapper::Snapper(SnapManager *sm, Geom::Coord const /*t*/) : - _snapmanager(sm), - _snap_enabled(true), - _snap_visible_only(true) + _snapmanager(sm), + _snap_enabled(true), + _snap_visible_only(true) { g_assert(_snapmanager != NULL); } diff --git a/src/snapper.h b/src/snapper.h index 1801f309c..47c1c514e 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -20,6 +20,7 @@ #include "snapped-line.h" #include "snapped-curve.h" #include "snap-preferences.h" +#include "snap-candidate.h" struct SnappedConstraints { std::list<Inkscape::SnappedPoint> points; @@ -34,14 +35,13 @@ struct SPItem; namespace Inkscape { - /// Parent for classes that can snap points to something class Snapper { public: - Snapper() {} - Snapper(SnapManager *sm, ::Geom::Coord const t); - virtual ~Snapper() {} + Snapper() {} + Snapper(SnapManager *sm, ::Geom::Coord const t); + virtual ~Snapper() {} virtual Geom::Coord getSnapperTolerance() const = 0; //returns the tolerance of the snapper in screen pixels (i.e. independent of zoom) virtual bool getSnapperAlwaysSnap() const = 0; //if true, then the snapper will always snap, regardless of its tolerance @@ -53,18 +53,16 @@ public: // These four methods are only used for grids, for which snapping can be enabled individually void setEnabled(bool s); - void setSnapVisibleOnly(bool s); + void setSnapVisibleOnly(bool s); bool getEnabled() const {return _snap_enabled;} bool getSnapVisibleOnly() const {return _snap_visible_only;} virtual void freeSnap(SnappedConstraints &/*sc*/, SnapPreferences::PointType const &/*t*/, - Geom::Point const &/*p*/, - SnapSourceType const &/*source_type*/, - bool const &/*first_point*/, + Inkscape::SnapCandidatePoint const &/*p*/, Geom::OptRect const &/*bbox_to_snap*/, std::vector<SPItem const *> const */*it*/, - std::vector<std::pair<Geom::Point, int> > */*unselected_nodes*/) const {}; + std::vector<SnapCandidatePoint> */*unselected_nodes*/) const {}; class ConstraintLine { @@ -91,9 +89,9 @@ public: } Geom::Point projection(Geom::Point const &p) const { // returns the projection of p on this constraintline - Geom::Point const p1_on_cl = _has_point ? _point : p; - Geom::Point const p2_on_cl = p1_on_cl + _direction; - return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); + Geom::Point const p1_on_cl = _has_point ? _point : p; + Geom::Point const p2_on_cl = p1_on_cl + _direction; + return Geom::projection(p, Geom::Line(p1_on_cl, p2_on_cl)); } private: @@ -104,20 +102,18 @@ public: }; virtual void constrainedSnap(SnappedConstraints &/*sc*/, - SnapPreferences::PointType const &/*t*/, - Geom::Point const &/*p*/, - SnapSourceType const &/*source_type*/, - bool const &/*first_point*/, + SnapPreferences::PointType const &/*t*/, + Inkscape::SnapCandidatePoint const &/*p*/, Geom::OptRect const &/*bbox_to_snap*/, ConstraintLine const &/*c*/, std::vector<SPItem const *> const */*it*/) const {}; protected: - SnapManager *_snapmanager; + SnapManager *_snapmanager; - // This is only used for grids, for which snapping can be enabled individually - bool _snap_enabled; ///< true if this snapper is enabled, otherwise false - bool _snap_visible_only; + // This is only used for grids, for which snapping can be enabled individually + bool _snap_enabled; ///< true if this snapper is enabled, otherwise false + bool _snap_visible_only; }; } diff --git a/src/sp-clippath.h b/src/sp-clippath.h index 199b29f3b..02395f3d2 100644 --- a/src/sp-clippath.h +++ b/src/sp-clippath.h @@ -22,9 +22,10 @@ class SPClipPathView; #include "display/nr-arena-forward.h" +#include "libnr/nr-forward.h" #include "sp-object-group.h" #include "uri-references.h" -#include <libnr/nr-forward.h> +#include "xml/node.h" struct SPClipPath : public SPObjectGroup { class Reference; @@ -48,8 +49,39 @@ public: return (SPClipPath *)URIReference::getObject(); } protected: + /** + * If the owner element of this reference (the element with <... clippath="...">) + * is a child of the clippath it refers to, return false. + * \return false if obj is not a clippath or if obj is a parent of this + * reference's owner element. True otherwise. + */ virtual bool _acceptObject(SPObject *obj) const { - return SP_IS_CLIPPATH(obj); + if (!SP_IS_CLIPPATH(obj)) { + return false; + } + SPObject * const owner = this->getOwner(); + if (obj->isAncestorOf(owner)) { + Inkscape::XML::Node * const owner_repr = owner->repr; + Inkscape::XML::Node * const obj_repr = obj->repr; + gchar const * owner_name = NULL; + gchar const * owner_clippath = NULL; + gchar const * obj_name = NULL; + gchar const * obj_id = NULL; + if (owner_repr != NULL) { + owner_name = owner_repr->name(); + owner_clippath = owner_repr->attribute("clippath"); + } + if (obj_repr != NULL) { + obj_name = obj_repr->name(); + obj_id = obj_repr->attribute("id"); + } + g_warning("Ignoring recursive clippath reference " + "<%s clippath=\"%s\"> in <%s id=\"%s\">", + owner_name, owner_clippath, + obj_name, obj_id); + return false; + } + return true; } }; diff --git a/src/sp-conn-end-pair.cpp b/src/sp-conn-end-pair.cpp index 4dc0230ff..98b2aec26 100644 --- a/src/sp-conn-end-pair.cpp +++ b/src/sp-conn-end-pair.cpp @@ -12,6 +12,8 @@ #include <cstring> #include <string> +#include <iostream> +#include <glibmm/stringutils.h> #include "attributes.h" #include "sp-conn-end.h" @@ -26,10 +28,10 @@ SPConnEndPair::SPConnEndPair(SPPath *const owner) - : _invalid_path_connection() - , _path(owner) + : _path(owner) , _connRef(NULL) , _connType(SP_CONNECTOR_NOAVOID) + , _connCurvature(0.0) , _transformed_connection() { for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) { @@ -47,14 +49,6 @@ SPConnEndPair::~SPConnEndPair() delete this->_connEnd[handle_ix]; this->_connEnd[handle_ix] = NULL; } - if (_connRef) { - _connRef->removeFromGraph(); - delete _connRef; - _connRef = NULL; - } - - _invalid_path_connection.disconnect(); - _transformed_connection.disconnect(); } void @@ -68,6 +62,18 @@ SPConnEndPair::release() this->_connEnd[handle_ix]->href = NULL; this->_connEnd[handle_ix]->ref.detach(); } + + // If the document is being destroyed then the router instance + // and the ConnRefs will have been destroyed with it. + const bool routerInstanceExists = (_path->document->router != NULL); + + if (_connRef && routerInstanceExists) { + _connRef->removeFromGraph(); + delete _connRef; + } + _connRef = NULL; + + _transformed_connection.disconnect(); } void @@ -76,16 +82,17 @@ sp_conn_end_pair_build(SPObject *object) sp_object_read_attr(object, "inkscape:connector-type"); sp_object_read_attr(object, "inkscape:connection-start"); sp_object_read_attr(object, "inkscape:connection-end"); + sp_object_read_attr(object, "inkscape:connector-curvature"); } static void -avoid_conn_move(Geom::Matrix const */*mp*/, SPItem *moved_item) +avoid_conn_transformed(Geom::Matrix const */*mp*/, SPItem *moved_item) { - // Reroute connector SPPath *path = SP_PATH(moved_item); - path->connEndPair.makePathInvalid(); - sp_conn_adjust_invalid_path(path); + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } } @@ -93,16 +100,40 @@ void SPConnEndPair::setAttr(unsigned const key, gchar const *const value) { if (key == SP_ATTR_CONNECTOR_TYPE) { - if (value && (strcmp(value, "polyline") == 0)) { - _connType = SP_CONNECTOR_POLYLINE; - - Avoid::Router *router = _path->document->router; - GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id); - _connRef = new Avoid::ConnRef(router, itemID); - _invalid_path_connection = connectInvalidPath( - sigc::ptr_fun(&sp_conn_adjust_invalid_path)); - _transformed_connection = _path->connectTransformed( - sigc::ptr_fun(&avoid_conn_move)); + if (value && (strcmp(value, "polyline") == 0 || strcmp(value, "orthogonal") == 0)) { + int newconnType = strcmp(value, "polyline") ? SP_CONNECTOR_ORTHOGONAL : SP_CONNECTOR_POLYLINE; + + if (!_connRef) + { + _connType = newconnType; + Avoid::Router *router = _path->document->router; + GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id); + _connRef = new Avoid::ConnRef(router, itemID); + switch (newconnType) + { + case SP_CONNECTOR_POLYLINE: + _connRef->setRoutingType(Avoid::ConnType_PolyLine); + break; + case SP_CONNECTOR_ORTHOGONAL: + _connRef->setRoutingType(Avoid::ConnType_Orthogonal); + } + _transformed_connection = _path->connectTransformed( + sigc::ptr_fun(&avoid_conn_transformed)); + } + else + if (newconnType != _connType) + { + _connType = newconnType; + switch (newconnType) + { + case SP_CONNECTOR_POLYLINE: + _connRef->setRoutingType(Avoid::ConnType_PolyLine); + break; + case SP_CONNECTOR_ORTHOGONAL: + _connRef->setRoutingType(Avoid::ConnType_Orthogonal); + } + sp_conn_reroute_path(_path); + } } else { _connType = SP_CONNECTOR_NOAVOID; @@ -111,17 +142,25 @@ SPConnEndPair::setAttr(unsigned const key, gchar const *const value) _connRef->removeFromGraph(); delete _connRef; _connRef = NULL; - _invalid_path_connection.disconnect(); _transformed_connection.disconnect(); } } return; - + } + else if (key == SP_ATTR_CONNECTOR_CURVATURE) { + if (value) { + _connCurvature = g_strtod(value, NULL); + if (_connRef && _connRef->isInitialised()) { + // Redraw the connector, but only if it has been initialised. + sp_conn_reroute_path(_path); + } + } + return; } unsigned const handle_ix = key - SP_ATTR_CONNECTION_START; g_assert( handle_ix <= 1 ); - this->_connEnd[handle_ix]->setAttacherHref(value); + this->_connEnd[handle_ix]->setAttacherHref(value, _path); } void @@ -131,11 +170,18 @@ SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const if (this->_connEnd[handle_ix]->ref.getURI()) { char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-end"}; - gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString(); - repr->setAttribute(attr_strs[handle_ix], uri_string); - g_free(uri_string); + std::ostringstream ostr; + ostr<<this->_connEnd[handle_ix]->ref.getURI()->toString()<<"_"<< + (this->_connEnd[handle_ix]->type == ConnPointDefault ? "d":"u") << + "_" << this->_connEnd[handle_ix]->id; + + + repr->setAttribute(attr_strs[handle_ix], ostr.str().c_str()); } } + repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature).c_str()); + if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL) + repr->setAttribute("inkscape:connector-type", _connType == SP_CONNECTOR_POLYLINE ? "polyline" : "orthogonal" ); } void @@ -161,19 +207,14 @@ SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const { void SPConnEndPair::getEndpoints(Geom::Point endPts[]) const { - SPCurve *curve = _path->curve; + SPCurve *curve = _path->original_curve ? _path->original_curve : _path->curve; SPItem *h2attItem[2]; getAttachedItems(h2attItem); for (unsigned h = 0; h < 2; ++h) { if ( h2attItem[h] ) { - Geom::OptRect bbox = h2attItem[h]->getBounds(sp_item_i2doc_affine(h2attItem[h])); - if (bbox) { - endPts[h] = bbox->midpoint(); - } else { - // FIXME - endPts[h] = Geom::Point(0, 0); - } + g_assert(h2attItem[h]->avoidRef); + endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(_connEnd[h]->type, _connEnd[h]->id); } else { @@ -187,37 +228,40 @@ SPConnEndPair::getEndpoints(Geom::Point endPts[]) const { } } -sigc::connection -SPConnEndPair::connectInvalidPath(sigc::slot<void, SPPath *> slot) -{ - return _invalid_path_signal.connect(slot); +gdouble +SPConnEndPair::getCurvature(void) const { + return _connCurvature; } -static void emitPathInvalidationNotification(void *ptr) +SPConnEnd** +SPConnEndPair::getConnEnds(void) { - // We emit a signal here rather than just calling the reroute function - // since this allows all the movement action computation to happen, - // then all connectors (that require it) will be rerouted. Otherwise, - // one connector could get rerouted several times as a result of - // dragging a couple of shapes. + return _connEnd; +} - SPPath *path = SP_PATH(ptr); - path->connEndPair._invalid_path_signal.emit(path); +bool +SPConnEndPair::isOrthogonal(void) const { + return _connType == SP_CONNECTOR_ORTHOGONAL; } -void -SPConnEndPair::rerouteFromManipulation(void) + +static void redrawConnectorCallback(void *ptr) { - _connRef->makePathInvalid(); - sp_conn_adjust_path(_path); + SPPath *path = SP_PATH(ptr); + if (path->document == NULL) { + // This can happen when the document is being destroyed. + return; + } + sp_conn_redraw_path(path); } void -SPConnEndPair::reroute(void) +SPConnEndPair::rerouteFromManipulation(void) { - sp_conn_adjust_path(_path); + sp_conn_reroute_path_immediate(_path); } + // Called from sp_path_update to initialise the endpoints. void SPConnEndPair::update(void) @@ -231,8 +275,8 @@ SPConnEndPair::update(void) Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]); Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]); - _connRef->lateSetup(src, dst); - _connRef->setCallback(&emitPathInvalidationNotification, _path); + _connRef->setEndpoints(src, dst); + _connRef->setCallback(&redrawConnectorCallback, _path); } // Store the ID of the objects attached to the connector. storeIds(); @@ -243,19 +287,25 @@ SPConnEndPair::update(void) void SPConnEndPair::storeIds(void) { if (_connEnd[0]->href) { + gchar ** href_strarray = NULL; + href_strarray = g_strsplit(_connEnd[0]->href, "_", 0); // href begins with a '#' which we don't want. - const char *startId = _connEnd[0]->href + 1; + const char *startId = href_strarray[0] + 1; GQuark itemId = g_quark_from_string(startId); _connRef->setEndPointId(Avoid::VertID::src, itemId); + g_strfreev(href_strarray); } else { _connRef->setEndPointId(Avoid::VertID::src, 0); } if (_connEnd[1]->href) { + gchar ** href_strarray = NULL; + href_strarray = g_strsplit(_connEnd[1]->href, "_", 0); // href begins with a '#' which we don't want. - const char *endId = _connEnd[1]->href + 1; + const char *endId = href_strarray[0] + 1; GQuark itemId = g_quark_from_string(endId); _connRef->setEndPointId(Avoid::VertID::tar, itemId); + g_strfreev(href_strarray); } else { _connRef->setEndPointId(Avoid::VertID::tar, 0); @@ -278,15 +328,55 @@ SPConnEndPair::makePathInvalid(void) _connRef->makePathInvalid(); } + +// Redraws the curve along the recalculated route +// Straight or curved +void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature) +{ + bool straight = curvature<1e-3; + + Avoid::PolyLine route = connRef->displayRoute(); + if (!straight) + route = route.curvedPolyline(curvature); + connRef->calcRouteDist(); + + curve->reset(); + + curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) ); + int pn = route.size(); + for (int i = 1; i < pn; ++i) { + Geom::Point p(route.ps[i].x, route.ps[i].y); + if (straight) { + curve->lineto( p ); + } + else { + switch (route.ts[i]) { + case 'M': + curve->moveto( p ); + break; + case 'L': + curve->lineto( p ); + break; + case 'C': + g_assert( i+2<pn ); + curve->curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y), + Geom::Point(route.ps[i+2].x, route.ps[i+2].y) ); + i+=2; + break; + } + } + } +} + + void -SPConnEndPair::reroutePath(void) +SPConnEndPair::tellLibavoidNewEndpoints(const bool processTransaction) { if (!isAutoRoutingConn()) { // Do nothing return; } - - SPCurve *curve = _path->curve; + makePathInvalid(); Geom::Point endPt[2]; getEndpoints(endPt); @@ -294,26 +384,34 @@ SPConnEndPair::reroutePath(void) Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]); Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]); - _connRef->updateEndPoint(Avoid::VertID::src, src); - _connRef->updateEndPoint(Avoid::VertID::tar, dst); - - _connRef->generatePath(src, dst); - - Avoid::PolyLine route = _connRef->route(); - _connRef->calcRouteDist(); + _connRef->setEndpoints(src, dst); + if (processTransaction) + { + _connRef->router()->processTransaction(); + } + return; +} - curve->reset(); - curve->moveto(endPt[0]); - for (int i = 1; i < route.pn; ++i) { - Geom::Point p(route.ps[i].x, route.ps[i].y); - curve->lineto(p); +bool +SPConnEndPair::reroutePathFromLibavoid(void) +{ + if (!isAutoRoutingConn()) { + // Do nothing + return false; } + SPCurve *curve = _path->original_curve ?_path->original_curve : _path->curve; + + recreateCurve( curve, _connRef, _connCurvature ); + Geom::Matrix doc2item = sp_item_i2doc_affine(SP_ITEM(_path)).inverse(); curve->transform(doc2item); + + return true; } + /* Local Variables: mode:c++ diff --git a/src/sp-conn-end-pair.h b/src/sp-conn-end-pair.h index 9b9e181db..3b011ed17 100644 --- a/src/sp-conn-end-pair.h +++ b/src/sp-conn-end-pair.h @@ -28,6 +28,7 @@ class Node; } } +extern void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, gdouble curvature); class SPConnEndPair { public: @@ -38,19 +39,16 @@ public: void writeRepr(Inkscape::XML::Node *const repr) const; void getAttachedItems(SPItem *[2]) const; void getEndpoints(Geom::Point endPts[]) const; - void reroutePath(void); + gdouble getCurvature(void) const; + SPConnEnd** getConnEnds(void); + bool isOrthogonal(void) const; + friend void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, gdouble curvature); + void tellLibavoidNewEndpoints(const bool processTransaction = false); + bool reroutePathFromLibavoid(void); void makePathInvalid(void); void update(void); bool isAutoRoutingConn(void); void rerouteFromManipulation(void); - void reroute(void); - sigc::connection connectInvalidPath(sigc::slot<void, SPPath *> slot); - - // A signal emited by a call back from libavoid. Used to let - // connectors know when they need to reroute themselves. - sigc::signal<void, SPPath *> _invalid_path_signal; - // A sigc connection to listen for connector path invalidation. - sigc::connection _invalid_path_connection; private: SPConnEnd *_connEnd[2]; @@ -61,6 +59,7 @@ private: Avoid::ConnRef *_connRef; int _connType; + gdouble _connCurvature; // A sigc connection for transformed signal. sigc::connection _transformed_connection; @@ -74,8 +73,9 @@ void sp_conn_end_pair_build(SPObject *object); // _connType options: enum { - SP_CONNECTOR_NOAVOID, // Basic connector - a straight line. - SP_CONNECTOR_POLYLINE // Object avoiding polyline. + SP_CONNECTOR_NOAVOID, // Basic connector - a straight line. + SP_CONNECTOR_POLYLINE, // Object avoiding polyline. + SP_CONNECTOR_ORTHOGONAL // Object avoiding orthogonal polyline (only horizontal and verical segments). }; diff --git a/src/sp-conn-end.cpp b/src/sp-conn-end.cpp index 0b420a98e..224442eb8 100644 --- a/src/sp-conn-end.cpp +++ b/src/sp-conn-end.cpp @@ -16,7 +16,7 @@ #include "2geom/path-intersection.h" -static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]); +static void change_endpts(SPCurve *const curve, double const endPos[2]); SPConnEnd::SPConnEnd(SPObject *const owner) : ref(owner), @@ -39,45 +39,37 @@ get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[ } -static bool try_get_intersect_point_with_item_recursive(SPCurve *conn_curve, SPItem& item, - const Geom::Matrix& item_transform, const bool at_start, double* intersect_pos, - unsigned *intersect_index) { - - double initial_pos = (at_start) ? 0.0 : std::numeric_limits<double>::max(); +static bool try_get_intersect_point_with_item_recursive(Geom::PathVector& conn_pv, SPItem* item, + const Geom::Matrix& item_transform, double& intersect_pos) { + double initial_pos = intersect_pos; // if this is a group... - if (SP_IS_GROUP(&item)) { - SPGroup* group = SP_GROUP(&item); - + if (SP_IS_GROUP(item)) { + SPGroup* group = SP_GROUP(item); + // consider all first-order children - double child_pos = initial_pos; - unsigned child_index; + double child_pos = 0.0; for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) { SPItem* child_item = SP_ITEM(i->data); - try_get_intersect_point_with_item_recursive(conn_curve, *child_item, - item_transform * child_item->transform, at_start, &child_pos, &child_index); - if (fabs(initial_pos - child_pos) > fabs(initial_pos - *intersect_pos)) { - // It is further away from the initial point than the current intersection - // point (i.e. the "outermost" intersection), so use this one. - *intersect_pos = child_pos; - *intersect_index = child_index; - } + try_get_intersect_point_with_item_recursive(conn_pv, child_item, + item_transform * child_item->transform, child_pos); + if (intersect_pos < child_pos) + intersect_pos = child_pos; } - return *intersect_pos != initial_pos; + return intersect_pos != initial_pos; } - // if this is a shape... - if (!SP_IS_SHAPE(&item)) return false; + // if this is not a shape, nothing to be done + if (!SP_IS_SHAPE(item)) return false; // make sure it has an associated curve - SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(&item)); + SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(item)); if (!item_curve) return false; // apply transformations (up to common ancestor) item_curve->transform(item_transform); const Geom::PathVector& curve_pv = item_curve->get_pathvector(); - const Geom::PathVector& conn_pv = conn_curve->get_pathvector(); Geom::CrossingSet cross = crossings(conn_pv, curve_pv); // iterate over all Crossings for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); i++) { @@ -85,18 +77,14 @@ static bool try_get_intersect_point_with_item_recursive(SPCurve *conn_curve, SPI for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); i++) { const Geom::Crossing& cr_pt = *i; - if (fabs(initial_pos - cr_pt.ta) > fabs(initial_pos - *intersect_pos)) { - // It is further away from the initial point than the current intersection - // point (i.e. the "outermost" intersection), so use this one. - *intersect_pos = cr_pt.ta; - *intersect_index = cr_pt.a; - } + if ( intersect_pos < cr_pt.ta) + intersect_pos = cr_pt.ta; } } item_curve->unref(); - return *intersect_pos != initial_pos; + return intersect_pos != initial_pos; } @@ -104,22 +92,36 @@ static bool try_get_intersect_point_with_item_recursive(SPCurve *conn_curve, SPI // and the item given. If the item is a group, then the component items are considered. // The transforms given should be to a common ancestor of both the path and item. // -static bool try_get_intersect_point_with_item(SPPath& conn, SPItem& item, - const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform, - const bool at_start, double* intersect_pos, unsigned *intersect_index) { - - // We start with the intersection point either at the beginning or end of the - // path, depending on whether we are considering the source or target endpoint. - *intersect_pos = (at_start) ? 0.0 : std::numeric_limits<double>::max(); +static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, + const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform, + const bool at_start, double& intersect_pos) { // Copy the curve and apply transformations up to common ancestor. - SPCurve* conn_curve = conn.curve->copy(); + SPCurve* conn_curve = conn->curve->copy(); conn_curve->transform(conn_transform); + Geom::PathVector conn_pv = conn_curve->get_pathvector(); + + // If this is not the starting point, use Geom::Path::reverse() to reverse the path + if (!at_start) + { + // connectors are actually a single path, so consider the first element from a Geom::PathVector + conn_pv[0] = conn_pv[0].reverse(); + } + + // We start with the intersection point at the beginning of the path + intersect_pos = 0.0; + // Find the intersection. - bool result = try_get_intersect_point_with_item_recursive(conn_curve, item, item_transform, - at_start, intersect_pos, intersect_index); - + bool result = try_get_intersect_point_with_item_recursive(conn_pv, item, item_transform, intersect_pos); + + if (!result) + // No intersection point has been found (why?) + // just default to connector end + intersect_pos = 0; + // If not at the starting point, recompute position with respect to original path + if (!at_start) + intersect_pos = conn_pv[0].size() - intersect_pos; // Free the curve copy. conn_curve->unref(); @@ -128,17 +130,14 @@ static bool try_get_intersect_point_with_item(SPPath& conn, SPItem& item, static void -sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, - SPPath *const path, - bool const updatePathRepr = true) +sp_conn_get_route_and_redraw(SPPath *const path, + const bool updatePathRepr = true) { - // TODO: SPItem::getBounds gives the wrong result for some objects - // that have internal representations that are updated later - // by the sp_*_update functions, e.g., text. - sp_document_ensure_up_to_date(path->document); - // Get the new route around obstacles. - path->connEndPair.reroutePath(); + bool rerouted = path->connEndPair.reroutePathFromLibavoid(); + if (!rerouted) { + return; + } SPItem *h2attItem[2]; path->connEndPair.getAttachedItems(h2attItem); @@ -147,72 +146,80 @@ sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor)); - Geom::Point endPts[2] = { *(path->curve->first_point()), *(path->curve->last_point()) }; - + // Set sensible values incase there the connector ends are not + // attached to any shapes. + Geom::PathVector conn_pv = path->curve->get_pathvector(); + double endPos[2] = { 0, conn_pv[0].size() }; + + SPConnEnd** _connEnd = path->connEndPair.getConnEnds(); for (unsigned h = 0; h < 2; ++h) { - if (h2attItem[h]) { - // For each attached object, change the corresponding point to be - // at the outermost intersection with the object's path. - double intersect_pos; - unsigned intersect_index; + if (h2attItem[h] && _connEnd[h]->type == ConnPointDefault && _connEnd[h]->id == ConnPointPosCC) { Geom::Matrix h2i2anc = i2anc_affine(h2attItem[h], ancestor); - if ( try_get_intersect_point_with_item(*path, *h2attItem[h], h2i2anc, path2anc, - (h == 0), &intersect_pos, &intersect_index) ) { - const Geom::PathVector& curve = path->curve->get_pathvector(); - endPts[h] = curve[intersect_index].pointAt(intersect_pos); - } + try_get_intersect_point_with_item(path, h2attItem[h], h2i2anc, path2anc, + (h == 0), endPos[h]); } } - change_endpts(path->curve, endPts); + change_endpts(path->curve, endPos); if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); path->updateRepr(); + path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } } -// TODO: This triggering of makeInvalidPath could be cleaned up to be -// another option passed to move_compensate. + static void -sp_conn_end_shape_move_compensate(Geom::Matrix const *mp, SPItem *moved_item, +sp_conn_end_shape_move(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPPath *const path) { if (path->connEndPair.isAutoRoutingConn()) { - path->connEndPair.makePathInvalid(); + path->connEndPair.tellLibavoidNewEndpoints(); } - sp_conn_end_move_compensate(mp, moved_item, path); } void -sp_conn_adjust_invalid_path(SPPath *const path) +sp_conn_reroute_path(SPPath *const path) { - sp_conn_end_move_compensate(NULL, NULL, path); + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } } + void -sp_conn_adjust_path(SPPath *const path) +sp_conn_reroute_path_immediate(SPPath *const path) { if (path->connEndPair.isAutoRoutingConn()) { - path->connEndPair.makePathInvalid(); + bool processTransaction = true; + path->connEndPair.tellLibavoidNewEndpoints(processTransaction); } // Don't update the path repr or else connector dragging is slowed by // constant update of values to the xml editor, and each step is also // needlessly remembered by undo/redo. bool const updatePathRepr = false; - sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr); + sp_conn_get_route_and_redraw(path, updatePathRepr); +} + +void sp_conn_redraw_path(SPPath *const path) +{ + sp_conn_get_route_and_redraw(path); } static void -change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]) +change_endpts(SPCurve *const curve, double const endPos[2]) { -#if 0 - curve->reset(); - curve->moveto(h2endPt[0]); - curve->lineto(h2endPt[1]); -#else - curve->move_endpoints(h2endPt[0], h2endPt[1]); -#endif + // Use Geom::Path::portion to cut the curve at the end positions + if (endPos[0] > endPos[1]) + { + // Path is "negative", reset the curve and return + curve->reset(); + return; + } + const Geom::Path& old_path = curve->get_pathvector()[0]; + Geom::PathVector new_path_vector; + new_path_vector.push_back(old_path.portion(endPos[0], endPos[1])); + curve->set_pathvector(new_path_vector); } static void @@ -234,29 +241,151 @@ sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix) } void -SPConnEnd::setAttacherHref(gchar const *value) +SPConnEnd::setAttacherHref(gchar const *value, SPPath* /*path*/) { if ( value && href && ( strcmp(value, href) == 0 ) ) { /* No change, do nothing. */ } else { - g_free(href); - href = NULL; - if (value) { - // First, set the href field, because sp_conn_end_href_changed will need it. - href = g_strdup(value); - - // Now do the attaching, which emits the changed signal. - try { - ref.attach(Inkscape::URI(value)); - } catch (Inkscape::BadURIException &e) { - /* TODO: Proper error handling as per - * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for - * sp-use.) */ - g_warning("%s", e.what()); + if (!value) + { + ref.detach(); + g_free(href); + href = NULL; + } + else + { + + /* References to the connection points have the following format + #svguri_t_id, where #svguri is the id of the item the + connector is attached to, t is the type of the point, which + can be either "d" for default or "u" for user-defined, and + id is the local (inside the item) id of the connection point. + In the case of default points id represents the position on the + item (i.e. Top-Left, Centre-Centre, etc.). + */ + + gchar ** href_strarray = NULL; + if (href) + href_strarray = g_strsplit(href, "_", 0); + gchar ** value_strarray = g_strsplit(value, "_", 0); + + g_free(href); + href = NULL; + + bool changed = false; + bool validRef = true; + + if ( !href_strarray || g_strcmp0(href_strarray[0], value_strarray[0]) != 0 ) + { + // The href has changed, so update it. + changed = true; + // Set the href field, because sp_conn_end_href_changed will need it. + href = g_strdup(value); + // Now do the attaching, which emits the changed signal. + try { + ref.attach(Inkscape::URI(value_strarray[0])); + } catch (Inkscape::BadURIException &e) { + /* TODO: Proper error handling as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for + * sp-use.) */ + g_warning("%s", e.what()); + validRef = false; + } + } + // Check to see if the connection point changed and update it. + // + + if ( !value_strarray[1] ) + { + /* Treat the old references to connection points + as default points that connect to the centre + of the item. + */ + if ( type != ConnPointDefault ) + { + type = ConnPointDefault; + changed = true; + } + if ( id != ConnPointPosCC ) + { + id = ConnPointPosCC; + changed = true; + } + } + else + { + switch (value_strarray[1][0]) + { + case 'd': + if ( type != ConnPointDefault ) + { + type = ConnPointDefault; + changed = true; + } + break; + case 'u': + if ( type != ConnPointUserDefined) + { + type = ConnPointUserDefined; + changed = true; + } + break; + default: + g_warning("Bad reference to a connection point."); + validRef = false; + } + if ( value_strarray[2] ) + { + int newId = (int) g_ascii_strtod( value_strarray[2], 0 ); + if ( id != newId ) + { + id = newId; + changed = true; + } + + } + else + { + // We have a malformed reference to a connection point, + // emit a warning, clear href and detach ref. + changed = true; + g_warning("Bad reference to a connection point.");\ + validRef = false; + } + } + + if ( changed ) + { + // We still have to verify that the reference to the + // connection point is a valid one. + + // Get the item the connector is attached to + SPItem* item = ref.getObject(); + if ( item && !item->avoidRef->isValidConnPointId( type, id ) ) + { + g_warning("Bad reference to a connection point."); + validRef = false; + } +/* else + // Update the connector + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } +*/ + } + + if ( !validRef ) + { ref.detach(); + g_free(href); + href = NULL; } - } else { - ref.detach(); + else + if (!href) + href = g_strdup(value); + + g_strfreev(href_strarray); + g_strfreev(value_strarray); } } } @@ -277,7 +406,7 @@ sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted), SP_OBJECT(path), handle_ix)); connEnd._transformed_connection - = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move_compensate), + = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move), path)); } } diff --git a/src/sp-conn-end.h b/src/sp-conn-end.h index a565b6404..5e9dbb9da 100644 --- a/src/sp-conn-end.h +++ b/src/sp-conn-end.h @@ -5,6 +5,8 @@ #include <sigc++/connection.h> #include "sp-use-reference.h" +#include "connection-points.h" +#include "conn-avoid-ref.h" class SPConnEnd { @@ -14,6 +16,15 @@ public: SPUseReference ref; gchar *href; + /* In the following, type refers to connection point type, + i.e. default (one of the 9 combinations of right, centre, + left, top, bottom) or user-defined. The id serves to identify + the connection point in a list of connection points. + */ + + ConnPointType type; + int id; + /** Change of href string (not a modification of the attributes of the referrent). */ sigc::connection _changed_connection; @@ -23,7 +34,7 @@ public: /** A sigc connection for transformed signal, used to do move compensation. */ sigc::connection _transformed_connection; - void setAttacherHref(gchar const *); + void setAttacherHref(gchar const *, SPPath *); private: SPConnEnd(SPConnEnd const &); @@ -32,8 +43,9 @@ private: void sp_conn_end_href_changed(SPObject *old_ref, SPObject *ref, SPConnEnd *connEnd, SPPath *path, unsigned const handle_ix); -void sp_conn_adjust_invalid_path(SPPath *const path); -void sp_conn_adjust_path(SPPath *const path); +void sp_conn_reroute_path(SPPath *const path); +void sp_conn_reroute_path_immediate(SPPath *const path); +void sp_conn_redraw_path(SPPath *const path); void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix); diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp index 12ba0ed0e..88fc59f17 100644 --- a/src/sp-ellipse.cpp +++ b/src/sp-ellipse.cpp @@ -29,11 +29,10 @@ #include <glibmm/i18n.h> #include <2geom/transforms.h> #include <2geom/pathvector.h> - #include "document.h" #include "sp-ellipse.h" - #include "preferences.h" +#include "snap-candidate.h" /* Common parent class */ @@ -73,7 +72,7 @@ static void sp_genericellipse_init(SPGenericEllipse *ellipse); static void sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags); -static void sp_genericellipse_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_genericellipse_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_genericellipse_set_shape(SPShape *shape); static void sp_genericellipse_update_patheffect (SPLPEItem *lpeitem, bool write); @@ -270,15 +269,15 @@ static void sp_genericellipse_set_shape(SPShape *shape) curve->unref(); } -static void sp_genericellipse_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_genericellipse_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { g_assert(item != NULL); g_assert(SP_IS_GENERICELLIPSE(item)); // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes - if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { - return; - } + if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { + return; + } SPGenericEllipse *ellipse = SP_GENERICELLIPSE(item); sp_genericellipse_normalize(ellipse); @@ -305,19 +304,19 @@ static void sp_genericellipse_snappoints(SPItem const *item, bool const target, // Snap to the 4 quadrant points of the ellipse, but only if the arc // spans far enough to include them if (snapprefs->getSnapToItemNode()) { //TODO: Make a separate snap option toggle for this? - double angle = 0; - for (angle = 0; angle < SP_2PI; angle += M_PI_2) { - if (angle >= ellipse->start && angle <= ellipse->end) { - pt = Geom::Point(cx + cos(angle)*rx, cy + sin(angle)*ry) * i2d; - p.push_back(std::make_pair(pt, target ? int(Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT) : int(Inkscape::SNAPSOURCE_ELLIPSE_QUADRANT_POINT))); - } - } + double angle = 0; + for (angle = 0; angle < SP_2PI; angle += M_PI_2) { + if (angle >= ellipse->start && angle <= ellipse->end) { + pt = Geom::Point(cx + cos(angle)*rx, cy + sin(angle)*ry) * i2d; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ELLIPSE_QUADRANT_POINT, Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)); + } + } } // Add the centre, if we have a closed slice or when explicitly asked for if ((snapprefs->getSnapToItemNode() && slice && ellipse->closed) || snapprefs->getSnapObjectMidpoints()) { - pt = Geom::Point(cx, cy) * i2d; - p.push_back(std::make_pair(pt, target ? int(Inkscape::SNAPTARGET_CENTER) : int(Inkscape::SNAPSOURCE_CENTER))); + pt = Geom::Point(cx, cy) * i2d; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_CENTER, Inkscape::SNAPTARGET_CENTER)); } // And if we have a slice, also snap to the endpoints @@ -325,12 +324,12 @@ static void sp_genericellipse_snappoints(SPItem const *item, bool const target, // Add the start point, if it's not coincident with a quadrant point if (fmod(ellipse->start, M_PI_2) != 0.0 ) { pt = Geom::Point(cx + cos(ellipse->start)*rx, cy + sin(ellipse->start)*ry) * i2d; - p.push_back(std::make_pair(pt, target ? int(Inkscape::SNAPTARGET_NODE_CUSP) : int(Inkscape::SNAPSOURCE_NODE_CUSP))); + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } // Add the end point, if it's not coincident with a quadrant point if (fmod(ellipse->end, M_PI_2) != 0.0 ) { pt = Geom::Point(cx + cos(ellipse->end)*rx, cy + sin(ellipse->end)*ry) * i2d; - p.push_back(std::make_pair(pt, target ? int(Inkscape::SNAPTARGET_NODE_CUSP) : int(Inkscape::SNAPSOURCE_NODE_CUSP))); + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } } } diff --git a/src/sp-flowtext.cpp b/src/sp-flowtext.cpp index 6af2f7169..b01146d60 100644 --- a/src/sp-flowtext.cpp +++ b/src/sp-flowtext.cpp @@ -27,7 +27,7 @@ #include "sp-rect.h" #include "text-tag-attributes.h" #include "text-chemistry.h" - +#include "text-editing.h" #include "livarot/Shape.h" @@ -49,6 +49,7 @@ static void sp_flowtext_set(SPObject *object, unsigned key, gchar const *value); static void sp_flowtext_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags); static void sp_flowtext_print(SPItem *item, SPPrintContext *ctx); static gchar *sp_flowtext_description(SPItem *item); +static void sp_flowtext_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static NRArenaItem *sp_flowtext_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags); static void sp_flowtext_hide(SPItem *item, unsigned key); @@ -98,6 +99,7 @@ sp_flowtext_class_init(SPFlowtextClass *klass) item_class->bbox = sp_flowtext_bbox; item_class->print = sp_flowtext_print; item_class->description = sp_flowtext_description; + item_class->snappoints = sp_flowtext_snappoints; item_class->show = sp_flowtext_show; item_class->hide = sp_flowtext_hide; } @@ -372,10 +374,27 @@ static gchar *sp_flowtext_description(SPItem *item) { Inkscape::Text::Layout const &layout = SP_FLOWTEXT(item)->layout; int const nChars = layout.iteratorToCharIndex(layout.end()); - if (SP_FLOWTEXT(item)->has_internal_frame()) - return g_strdup_printf(ngettext("<b>Flowed text</b> (%d character)", "<b>Flowed text</b> (%d characters)", nChars), nChars); - else - return g_strdup_printf(ngettext("<b>Linked flowed text</b> (%d character)", "<b>Linked flowed text</b> (%d characters)", nChars), nChars); + + char const *trunc = (layout.inputTruncated()) ? _(" [truncated]") : ""; + + if (SP_FLOWTEXT(item)->has_internal_frame()) { + return g_strdup_printf(ngettext("<b>Flowed text</b> (%d character%s)", "<b>Flowed text</b> (%d characters%s)", nChars), nChars, trunc); + } else { + return g_strdup_printf(ngettext("<b>Linked flowed text</b> (%d character%s)", "<b>Linked flowed text</b> (%d characters%s)", nChars), nChars, trunc); + } +} + +static void sp_flowtext_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/) +{ + // Choose a point on the baseline for snapping from or to, with the horizontal position + // of this point depending on the text alignment (left vs. right) + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) item); + if (layout != NULL && layout->outputExists()) { + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + if (pt) { + p.push_back(Inkscape::SnapCandidatePoint((*pt) * sp_item_i2d_affine(item), Inkscape::SNAPSOURCE_TEXT_BASELINE, Inkscape::SNAPTARGET_TEXT_BASELINE)); + } + } } static NRArenaItem * diff --git a/src/sp-font-face.cpp b/src/sp-font-face.cpp index 1ec6f4601..704985b8c 100644 --- a/src/sp-font-face.cpp +++ b/src/sp-font-face.cpp @@ -13,7 +13,7 @@ * http://www.w3.org/TR/SVG/fonts.html#FontFaceElement * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008, Felipe C. da S. Sanches * diff --git a/src/sp-font-face.h b/src/sp-font-face.h index 8fe1c752f..e492ba091 100644 --- a/src/sp-font-face.h +++ b/src/sp-font-face.h @@ -14,7 +14,7 @@ * http://www.w3.org/TR/SVG/fonts.html#FontFaceElement * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/sp-font.cpp b/src/sp-font.cpp index 75fb18638..de272c72f 100644 --- a/src/sp-font.cpp +++ b/src/sp-font.cpp @@ -8,7 +8,7 @@ * SVG <font> element implementation * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008, Felipe C. da S. Sanches * diff --git a/src/sp-font.h b/src/sp-font.h index fad7ead42..a0f895a52 100644 --- a/src/sp-font.h +++ b/src/sp-font.h @@ -9,7 +9,7 @@ * SVG <font> element implementation * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/sp-glyph-kerning.cpp b/src/sp-glyph-kerning.cpp index 6d08f212c..872efc853 100644 --- a/src/sp-glyph-kerning.cpp +++ b/src/sp-glyph-kerning.cpp @@ -10,7 +10,7 @@ * W3C SVG 1.1 spec, page 476, section 20.7 * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008, Felipe C. da S. Sanches * diff --git a/src/sp-glyph-kerning.h b/src/sp-glyph-kerning.h index ec0866c2c..ce9b4bb15 100644 --- a/src/sp-glyph-kerning.h +++ b/src/sp-glyph-kerning.h @@ -10,7 +10,7 @@ * SVG <hkern> and <vkern> elements implementation * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/sp-glyph.cpp b/src/sp-glyph.cpp index 8af78a0aa..37e266da0 100644 --- a/src/sp-glyph.cpp +++ b/src/sp-glyph.cpp @@ -9,7 +9,7 @@ * SVG <glyph> element implementation * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008, Felipe C. da S. Sanches * diff --git a/src/sp-glyph.h b/src/sp-glyph.h index 8c35a3a83..316204c23 100644 --- a/src/sp-glyph.h +++ b/src/sp-glyph.h @@ -10,7 +10,7 @@ * SVG <glyph> element implementation * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/sp-image.cpp b/src/sp-image.cpp index 65aad1e2d..daf5e9e88 100644 --- a/src/sp-image.cpp +++ b/src/sp-image.cpp @@ -44,7 +44,7 @@ #include <glibmm/i18n.h> #include "xml/quote.h" #include <xml/repr.h> - +#include "snap-candidate.h" #include "libnr/nr-matrix-fns.h" #include "io/sys.h" @@ -53,6 +53,13 @@ #include "color-profile.h" //#define DEBUG_LCMS #ifdef DEBUG_LCMS + + +#define DEBUG_MESSAGE(key, ...)\ +{\ + g_message( __VA_ARGS__ );\ +} + #include "preferences.h" #include <gtk/gtkmessagedialog.h> #endif // DEBUG_LCMS @@ -79,7 +86,7 @@ static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Doc static void sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags); static void sp_image_print (SPItem * item, SPPrintContext *ctx); static gchar * sp_image_description (SPItem * item); -static void sp_image_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); static Geom::Matrix sp_image_set_transform (SPItem *item, Geom::Matrix const &xform); static void sp_image_set_curve(SPImage *image); @@ -105,7 +112,7 @@ extern "C" #ifdef DEBUG_LCMS extern guint update_in_progress; -#define DEBUG_MESSAGE(key, ...) \ +#define DEBUG_MESSAGE_SCISLAC(key, ...) \ {\ Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ bool dump = prefs->getBool("/options/scislac/" #key);\ @@ -1325,7 +1332,7 @@ sp_image_update_canvas_image (SPImage *image) } } -static void sp_image_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const */*snapprefs*/) +static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/) { /* An image doesn't have any nodes to snap, but still we want to be able snap one image to another. Therefore we will create some snappoints at the corner, similar to a rect. If @@ -1347,12 +1354,10 @@ static void sp_image_snappoints(SPItem const *item, bool const target, SnapPoint double const x1 = x0 + image.width.computed; double const y1 = y0 + image.height.computed; Geom::Matrix const i2d (sp_item_i2d_affine (item)); - Geom::Point pt; - int type = target ? int(Inkscape::SNAPTARGET_CORNER) : int(Inkscape::SNAPSOURCE_CORNER); - p.push_back(std::make_pair(Geom::Point(x0, y0) * i2d, type)); - p.push_back(std::make_pair(Geom::Point(x0, y1) * i2d, type)); - p.push_back(std::make_pair(Geom::Point(x1, y1) * i2d, type)); - p.push_back(std::make_pair(Geom::Point(x1, y0) * i2d, type)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); } } diff --git a/src/sp-item-group.cpp b/src/sp-item-group.cpp index 7ac7880a7..a773bd4a2 100644 --- a/src/sp-item-group.cpp +++ b/src/sp-item-group.cpp @@ -67,10 +67,9 @@ static void sp_group_set(SPObject *object, unsigned key, char const *value); static void sp_group_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags); static void sp_group_print (SPItem * item, SPPrintContext *ctx); static gchar * sp_group_description (SPItem * item); -static Geom::Matrix sp_group_set_transform(SPItem *item, Geom::Matrix const &xform); static NRArenaItem *sp_group_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); static void sp_group_hide (SPItem * item, unsigned int key); -static void sp_group_snappoints (SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_group_snappoints (SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_group_update_patheffect(SPLPEItem *lpeitem, bool write); static void sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup, bool write); @@ -129,7 +128,6 @@ sp_group_class_init (SPGroupClass *klass) item_class->bbox = sp_group_bbox; item_class->print = sp_group_print; item_class->description = sp_group_description; - item_class->set_transform = sp_group_set_transform; item_class->show = sp_group_show; item_class->hide = sp_group_hide; item_class->snappoints = sp_group_snappoints; @@ -291,24 +289,6 @@ static gchar * sp_group_description (SPItem * item) return SP_GROUP(item)->group->getDescription(); } -static Geom::Matrix -sp_group_set_transform(SPItem *item, Geom::Matrix const &xform) -{ - Inkscape::Selection *selection = sp_desktop_selection(inkscape_active_desktop()); - persp3d_split_perspectives_according_to_selection(selection); - - Geom::Matrix last_trans; - sp_svg_transform_read(SP_OBJECT_REPR(item)->attribute("transform"), &last_trans); - Geom::Matrix inc_trans = last_trans.inverse()*xform; - - std::list<Persp3D *> plist = selection->perspList(); - for (std::list<Persp3D *>::iterator i = plist.begin(); i != plist.end(); ++i) { - persp3d_apply_affine_transformation(*i, inc_trans); - } - - return xform; -} - static void sp_group_set(SPObject *object, unsigned key, char const *value) { SPGroup *group = SP_GROUP(object); @@ -340,14 +320,14 @@ sp_group_hide (SPItem *item, unsigned int key) SP_GROUP(item)->group->hide(key); } -static void sp_group_snappoints (SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_group_snappoints (SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { for (SPObject const *o = sp_object_first_child(SP_OBJECT(item)); o != NULL; o = SP_OBJECT_NEXT(o)) { if (SP_IS_ITEM(o)) { - sp_item_snappoints(SP_ITEM(o), target, p, snapprefs); + sp_item_snappoints(SP_ITEM(o), p, snapprefs); } } } diff --git a/src/sp-item-notify-moveto.cpp b/src/sp-item-notify-moveto.cpp index 0bc08a235..628d77956 100644 --- a/src/sp-item-notify-moveto.cpp +++ b/src/sp-item-notify-moveto.cpp @@ -24,11 +24,11 @@ void sp_item_notify_moveto(SPItem &item, SPGuide const &mv_g, int const snappoin double const dir_lensq(dot(dir, dir)); g_return_if_fail( dir_lensq != 0 ); - SnapPointsWithType snappoints; - sp_item_snappoints(&item, false, snappoints, NULL); + std::vector<Inkscape::SnapCandidatePoint> snappoints; + sp_item_snappoints(&item, snappoints, NULL); g_return_if_fail( snappoint_ix < int(snappoints.size()) ); - double const pos0 = dot(dir, snappoints[snappoint_ix].first); + double const pos0 = dot(dir, snappoints[snappoint_ix].getPoint()); /// \todo effic: skip if mv_g is already satisfied. /* Translate along dir to make dot(dir, snappoints(item)[snappoint_ix]) == position. */ diff --git a/src/sp-item-rm-unsatisfied-cns.cpp b/src/sp-item-rm-unsatisfied-cns.cpp index 246453241..792a9d3bf 100644 --- a/src/sp-item-rm-unsatisfied-cns.cpp +++ b/src/sp-item-rm-unsatisfied-cns.cpp @@ -14,14 +14,14 @@ void sp_item_rm_unsatisfied_cns(SPItem &item) if (item.constraints.empty()) { return; } - SnapPointsWithType snappoints; - sp_item_snappoints(&item, false, snappoints, NULL); + std::vector<Inkscape::SnapCandidatePoint> snappoints; + sp_item_snappoints(&item, snappoints, NULL); for (unsigned i = item.constraints.size(); i--;) { g_assert( i < item.constraints.size() ); SPGuideConstraint const &cn = item.constraints[i]; int const snappoint_ix = cn.snappoint_ix; g_assert( snappoint_ix < int(snappoints.size()) ); - if (!approx_equal( sp_guide_distance_from_pt(cn.g, snappoints[snappoint_ix].first), 0) ) { + if (!approx_equal( sp_guide_distance_from_pt(cn.g, snappoints[snappoint_ix].getPoint()), 0) ) { remove_last(cn.g->attached_items, SPGuideAttachment(&item, cn.snappoint_ix)); g_assert( i < item.constraints.size() ); vector<SPGuideConstraint>::iterator const ei(&item.constraints[i]); diff --git a/src/sp-item-update-cns.cpp b/src/sp-item-update-cns.cpp index bebd65021..51da1679d 100644 --- a/src/sp-item-update-cns.cpp +++ b/src/sp-item-update-cns.cpp @@ -9,8 +9,8 @@ using std::vector; void sp_item_update_cns(SPItem &item, SPDesktop const &desktop) { - SnapPointsWithType snappoints; - sp_item_snappoints(&item, false, snappoints, NULL); + std::vector<Inkscape::SnapCandidatePoint> snappoints; + sp_item_snappoints(&item, snappoints, NULL); /* TODO: Implement the ordering. */ vector<SPGuideConstraint> found_cns; satisfied_guide_cns(desktop, snappoints, found_cns); diff --git a/src/sp-item.cpp b/src/sp-item.cpp index 395048280..c28940fca 100644 --- a/src/sp-item.cpp +++ b/src/sp-item.cpp @@ -90,7 +90,7 @@ static void sp_item_update(SPObject *object, SPCtx *ctx, guint flags); static Inkscape::XML::Node *sp_item_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); static gchar *sp_item_private_description(SPItem *item); -static void sp_item_private_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_item_private_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem); static SPItemView *sp_item_view_list_remove(SPItemView *list, SPItemView *view); @@ -412,6 +412,7 @@ sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) sp_object_read_attr(object, "inkscape:transform-center-x"); sp_object_read_attr(object, "inkscape:transform-center-y"); sp_object_read_attr(object, "inkscape:connector-avoid"); + sp_object_read_attr(object, "inkscape:connection-points"); if (((SPObjectClass *) (parent_class))->build) { (* ((SPObjectClass *) (parent_class))->build)(object, document, repr); @@ -514,6 +515,9 @@ sp_item_set(SPObject *object, unsigned key, gchar const *value) case SP_ATTR_CONNECTOR_AVOID: item->avoidRef->setAvoid(value); break; + case SP_ATTR_CONNECTION_POINTS: + item->avoidRef->setConnectionPoints(value); + break; case SP_ATTR_TRANSFORM_CENTER_X: if (value) { item->transform_center_x = g_strtod(value, NULL); @@ -945,7 +949,7 @@ Geom::OptRect sp_item_bbox_desktop(SPItem *item, SPItem::BBoxType type) return rect; } -static void sp_item_private_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const */*snapprefs*/) +static void sp_item_private_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/) { /* This will only be called if the derived class doesn't override this. * see for example sp_genericellipse_snappoints in sp-ellipse.cpp @@ -958,16 +962,15 @@ static void sp_item_private_snappoints(SPItem const *item, bool const target, Sn Geom::Point p1, p2; p1 = bbox->min(); p2 = bbox->max(); - int type = target ? int(Inkscape::SNAPSOURCE_CONVEX_HULL_CORNER) : int(Inkscape::SNAPSOURCE_CONVEX_HULL_CORNER); - p.push_back(std::make_pair(p1, type)); - p.push_back(std::make_pair(Geom::Point(p1[Geom::X], p2[Geom::Y]), type)); - p.push_back(std::make_pair(p2, type)); - p.push_back(std::make_pair(Geom::Point(p1[Geom::Y], p2[Geom::X]), type)); + p.push_back(Inkscape::SnapCandidatePoint(p1, Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(p1[Geom::X], p2[Geom::Y]), Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p2, Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(p2[Geom::X], p1[Geom::Y]), Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER)); } } -void sp_item_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +void sp_item_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { g_assert (item != NULL); g_assert (SP_IS_ITEM(item)); @@ -975,12 +978,12 @@ void sp_item_snappoints(SPItem const *item, bool const target, SnapPointsWithTyp // Get the snappoints of the item SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(item); if (item_class.snappoints) { - item_class.snappoints(item, target, p, snapprefs); + item_class.snappoints(item, p, snapprefs); } // Get the snappoints at the item's center if (snapprefs != NULL && snapprefs->getIncludeItemCenter()) { - p.push_back(std::make_pair(item->getCenter(), target ? int(Inkscape::SNAPTARGET_ROTATION_CENTER) : int(Inkscape::SNAPSOURCE_ROTATION_CENTER))); + p.push_back(Inkscape::SnapCandidatePoint(item->getCenter(), Inkscape::SNAPSOURCE_ROTATION_CENTER, Inkscape::SNAPTARGET_ROTATION_CENTER)); } // Get the snappoints of clipping paths and mask, if any @@ -995,15 +998,15 @@ void sp_item_snappoints(SPItem const *item, bool const target, SnapPointsWithTyp // obj is a group object, the children are the actual clippers for (SPObject *child = (*o)->children ; child ; child = child->next) { if (SP_IS_ITEM(child)) { - SnapPointsWithType p_clip_or_mask; + std::vector<Inkscape::SnapCandidatePoint> p_clip_or_mask; // Please note the recursive call here! - sp_item_snappoints(SP_ITEM(child), target, p_clip_or_mask, snapprefs); + sp_item_snappoints(SP_ITEM(child), p_clip_or_mask, snapprefs); // Take into account the transformation of the item being clipped or masked - for (SnapPointsWithType::const_iterator p_orig = p_clip_or_mask.begin(); p_orig != p_clip_or_mask.end(); p_orig++) { + for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator p_orig = p_clip_or_mask.begin(); p_orig != p_clip_or_mask.end(); p_orig++) { // All snappoints are in desktop coordinates, but the item's transformation is // in document coordinates. Hence the awkward construction below - Geom::Point pt = desktop->dt2doc((*p_orig).first) * sp_item_i2d_affine(item); - p.push_back(std::make_pair(pt, (*p_orig).second)); + Geom::Point pt = desktop->dt2doc((*p_orig).getPoint()) * sp_item_i2d_affine(item); + p.push_back(Inkscape::SnapCandidatePoint(pt, (*p_orig).getSourceType(), (*p_orig).getTargetType())); } } } diff --git a/src/sp-item.h b/src/sp-item.h index 639a1b4a2..faf64846e 100644 --- a/src/sp-item.h +++ b/src/sp-item.h @@ -26,14 +26,14 @@ #include <2geom/forward.h> #include <libnr/nr-convert2geom.h> #include <snap-preferences.h> -#include <snapped-point.h> +#include "snap-candidate.h" class SPGuideConstraint; struct SPClipPathReference; struct SPMaskReference; struct SPAvoidRef; struct SPPrintContext; -namespace Inkscape { class URIReference; } +namespace Inkscape { class URIReference;} enum { SP_EVENT_INVALID, @@ -171,8 +171,6 @@ private: mutable EvaluatedStatus _evaluated_status; }; -typedef std::vector<std::pair<Geom::Point, int> > SnapPointsWithType; // int is either of these enums: Inkscape::SnapTargetType or Inkscape::SnapSourceType - /// The SPItem vtable. struct SPItemClass { SPObjectClass parent_class; @@ -193,7 +191,7 @@ struct SPItemClass { /** Write to an iterator the points that should be considered for snapping * as the item's `nodes'. */ - void (* snappoints) (SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); + void (* snappoints) (SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); /** Apply the transform optimally, and return any residual transformation */ Geom::Matrix (* set_transform)(SPItem *item, Geom::Matrix const &transform); @@ -226,7 +224,7 @@ unsigned int sp_item_display_key_new(unsigned int numkeys); NRArenaItem *sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); void sp_item_invoke_hide(SPItem *item, unsigned int key); -void sp_item_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +void sp_item_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); void sp_item_adjust_pattern(SPItem *item, /* Geom::Matrix const &premul, */ Geom::Matrix const &postmul, bool set = false); void sp_item_adjust_gradient(SPItem *item, /* Geom::Matrix const &premul, */ Geom::Matrix const &postmul, bool set = false); diff --git a/src/sp-marker-loc.h b/src/sp-marker-loc.h index 98cab3746..b6877e5aa 100644 --- a/src/sp-marker-loc.h +++ b/src/sp-marker-loc.h @@ -5,6 +5,8 @@ * These enums are to allow us to have 4-element arrays that represent a set of marker locations * (all, start, mid, and end). This allows us to iterate through the array in places where we need * to do a process across all of the markers, instead of separate code stanzas for each. + * + * IMPORTANT: the code assumes that the locations have the values as written below! so don't change the values!!! */ enum SPMarkerLoc { SP_MARKER_LOC = 0, diff --git a/src/sp-mask.h b/src/sp-mask.h index d5bddd332..0b995f0ce 100644 --- a/src/sp-mask.h +++ b/src/sp-mask.h @@ -26,6 +26,7 @@ class SPMaskView; #include "libnr/nr-forward.h" #include "sp-object-group.h" #include "uri-references.h" +#include "xml/node.h" struct SPMask : public SPObjectGroup { unsigned int maskUnits_set : 1; @@ -50,8 +51,39 @@ public: return (SPMask *)URIReference::getObject(); } protected: + /** + * If the owner element of this reference (the element with <... mask="...">) + * is a child of the mask it refers to, return false. + * \return false if obj is not a mask or if obj is a parent of this + * reference's owner element. True otherwise. + */ virtual bool _acceptObject(SPObject *obj) const { - return SP_IS_MASK(obj); + if (!SP_IS_MASK(obj)) { + return false; + } + SPObject * const owner = this->getOwner(); + if (obj->isAncestorOf(owner)) { + Inkscape::XML::Node * const owner_repr = owner->repr; + Inkscape::XML::Node * const obj_repr = obj->repr; + gchar const * owner_name = NULL; + gchar const * owner_mask = NULL; + gchar const * obj_name = NULL; + gchar const * obj_id = NULL; + if (owner_repr != NULL) { + owner_name = owner_repr->name(); + owner_mask = owner_repr->attribute("mask"); + } + if (obj_repr != NULL) { + obj_name = obj_repr->name(); + obj_id = obj_repr->attribute("id"); + } + g_warning("Ignoring recursive mask reference " + "<%s mask=\"%s\"> in <%s id=\"%s\">", + owner_name, owner_mask, + obj_name, obj_id); + return false; + } + return true; } }; diff --git a/src/sp-metadata.cpp b/src/sp-metadata.cpp index 920b7d64d..426810c7d 100644 --- a/src/sp-metadata.cpp +++ b/src/sp-metadata.cpp @@ -188,8 +188,7 @@ sp_metadata_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML: debug("0x%08x",(unsigned int)object); //SPMetadata *metadata = SP_METADATA(object); - // only create a repr when we're writing out an Inkscape SVG - if ( flags & SP_OBJECT_WRITE_EXT && repr != SP_OBJECT_REPR(object) ) { + if ( repr != SP_OBJECT_REPR(object) ) { if (repr) { repr->mergeFrom(SP_OBJECT_REPR (object), "id"); } else { @@ -197,8 +196,9 @@ sp_metadata_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML: } } - if (((SPObjectClass *) metadata_parent_class)->write) + if (((SPObjectClass *) metadata_parent_class)->write) { ((SPObjectClass *) metadata_parent_class)->write(object, doc, repr, flags); + } return repr; } diff --git a/src/sp-missing-glyph.cpp b/src/sp-missing-glyph.cpp index ffc29a71e..d25a5f812 100644 --- a/src/sp-missing-glyph.cpp +++ b/src/sp-missing-glyph.cpp @@ -9,7 +9,7 @@ * SVG <missing-glyph> element implementation * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008, Felipe C. da S. Sanches * diff --git a/src/sp-missing-glyph.h b/src/sp-missing-glyph.h index 2a4cfde07..0b3f74360 100644 --- a/src/sp-missing-glyph.h +++ b/src/sp-missing-glyph.h @@ -10,7 +10,7 @@ * SVG <missing-glyph> element implementation * * Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Felipe C. da S. Sanches * diff --git a/src/sp-namedview.cpp b/src/sp-namedview.cpp index 47720c5d6..9b72a4157 100644 --- a/src/sp-namedview.cpp +++ b/src/sp-namedview.cpp @@ -304,6 +304,7 @@ static void sp_namedview_release(SPObject *object) static void sp_namedview_set(SPObject *object, unsigned int key, const gchar *value) { SPNamedView *nv = SP_NAMEDVIEW(object); + // TODO investigate why we grab this and then never use it SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX); switch (key) { diff --git a/src/sp-offset.cpp b/src/sp-offset.cpp index ae0f7bf19..556778676 100644 --- a/src/sp-offset.cpp +++ b/src/sp-offset.cpp @@ -83,7 +83,7 @@ static void sp_offset_update (SPObject * object, SPCtx * ctx, guint flags); static void sp_offset_release (SPObject * object); static gchar *sp_offset_description (SPItem * item); -static void sp_offset_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_offset_set_shape (SPShape * shape); static void refresh_offset_source(SPOffset* offset); @@ -718,10 +718,10 @@ sp_offset_set_shape(SPShape *shape) /** * Virtual snappoints function. */ -static void sp_offset_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { if (((SPItemClass *) parent_class)->snappoints) { - ((SPItemClass *) parent_class)->snappoints (item, target, p, snapprefs); + ((SPItemClass *) parent_class)->snappoints (item, p, snapprefs); } } diff --git a/src/sp-path.cpp b/src/sp-path.cpp index 2120ddd64..bbcb25356 100644 --- a/src/sp-path.cpp +++ b/src/sp-path.cpp @@ -295,6 +295,7 @@ sp_path_set(SPObject *object, unsigned int key, gchar const *value) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_CONNECTOR_TYPE: + case SP_ATTR_CONNECTOR_CURVATURE: case SP_ATTR_CONNECTION_START: case SP_ATTR_CONNECTION_END: path->connEndPair.setAttr(key, value); diff --git a/src/sp-rect.cpp b/src/sp-rect.cpp index aa026abb3..d42fd0e9f 100644 --- a/src/sp-rect.cpp +++ b/src/sp-rect.cpp @@ -46,7 +46,7 @@ static Geom::Matrix sp_rect_set_transform(SPItem *item, Geom::Matrix const &xfor static void sp_rect_convert_to_guides(SPItem *item); static void sp_rect_set_shape(SPShape *shape); -static void sp_rect_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_rect_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static SPShapeClass *parent_class; @@ -552,7 +552,7 @@ sp_rect_get_visible_height(SPRect *rect) /** * Sets the snappoint p to the unrounded corners of the rectangle */ -static void sp_rect_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_rect_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping @@ -565,9 +565,9 @@ static void sp_rect_snappoints(SPItem const *item, bool const target, SnapPoints g_assert(SP_IS_RECT(item)); // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes - if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { - return; - } + if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { + return; + } SPRect *rect = SP_RECT(item); @@ -578,28 +578,23 @@ static void sp_rect_snappoints(SPItem const *item, bool const target, SnapPoints Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d; Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d; - int type; - if (snapprefs->getSnapToItemNode()) { - type = target ? int(Inkscape::SNAPTARGET_CORNER) : int(Inkscape::SNAPSOURCE_CORNER); - p.push_back(std::make_pair(p0, type)); - p.push_back(std::make_pair(p1, type)); - p.push_back(std::make_pair(p2, type)); - p.push_back(std::make_pair(p3, type)); + p.push_back(Inkscape::SnapCandidatePoint(p0, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p1, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p2, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p3, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER)); } - if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) - type = target ? int(Inkscape::SNAPTARGET_LINE_MIDPOINT) : int(Inkscape::SNAPSOURCE_LINE_MIDPOINT); - p.push_back(std::make_pair((p0 + p1)/2, type)); - p.push_back(std::make_pair((p1 + p2)/2, type)); - p.push_back(std::make_pair((p2 + p3)/2, type)); - p.push_back(std::make_pair((p3 + p0)/2, type)); - } - - if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) - type = target ? int(Inkscape::SNAPTARGET_OBJECT_MIDPOINT) : int(Inkscape::SNAPSOURCE_OBJECT_MIDPOINT); - p.push_back(std::make_pair((p0 + p2)/2, type)); - } + if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) + p.push_back(Inkscape::SnapCandidatePoint((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + } + + if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) + p.push_back(Inkscape::SnapCandidatePoint((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + } } diff --git a/src/sp-script.cpp b/src/sp-script.cpp index a40132450..ad41b8021 100644 --- a/src/sp-script.cpp +++ b/src/sp-script.cpp @@ -4,7 +4,7 @@ * SVG <script> implementation * * Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2008 authors * diff --git a/src/sp-script.h b/src/sp-script.h index fa1906052..127eeedf7 100644 --- a/src/sp-script.h +++ b/src/sp-script.h @@ -5,7 +5,7 @@ * SVG <script> implementation * * Author: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Author * diff --git a/src/sp-shape.cpp b/src/sp-shape.cpp index 519002e9e..de5648137 100644 --- a/src/sp-shape.cpp +++ b/src/sp-shape.cpp @@ -68,7 +68,7 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const & void sp_shape_print (SPItem * item, SPPrintContext * ctx); static NRArenaItem *sp_shape_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); static void sp_shape_hide (SPItem *item, unsigned int key); -static void sp_shape_snappoints (SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_shape_snappoints (SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai); @@ -1149,7 +1149,7 @@ sp_shape_set_curve_insync (SPShape *shape, SPCurve *curve, unsigned int owner) /** * Return all nodes in a path that are to be considered for snapping */ -static void sp_shape_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_shape_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { g_assert(item != NULL); g_assert(SP_IS_SHAPE(item)); @@ -1161,7 +1161,7 @@ static void sp_shape_snappoints(SPItem const *item, bool const target, SnapPoint // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { - return; + return; } Geom::PathVector const &pathv = shape->curve->get_pathvector(); @@ -1170,20 +1170,16 @@ static void sp_shape_snappoints(SPItem const *item, bool const target, SnapPoint Geom::Matrix const i2d (sp_item_i2d_affine (item)); - int type; - - if (snapprefs->getSnapObjectMidpoints()) { - Geom::OptRect bbox = item->getBounds(sp_item_i2d_affine(item)); - if (bbox) { - type = target ? int(Inkscape::SNAPTARGET_OBJECT_MIDPOINT) : int(Inkscape::SNAPSOURCE_OBJECT_MIDPOINT); - p.push_back(std::make_pair(bbox->midpoint(), type)); - } - } + if (snapprefs->getSnapObjectMidpoints()) { + Geom::OptRect bbox = item->getBounds(sp_item_i2d_affine(item)); + if (bbox) { + p.push_back(Inkscape::SnapCandidatePoint(bbox->midpoint(), Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + } + } for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { if (snapprefs->getSnapToItemNode()) { - type = target ? int(Inkscape::SNAPTARGET_NODE_CUSP) : int(Inkscape::SNAPSOURCE_NODE_CUSP); - p.push_back(std::make_pair(path_it->initialPoint() * i2d, type)); + p.push_back(Inkscape::SnapCandidatePoint(path_it->initialPoint() * i2d, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve @@ -1202,17 +1198,15 @@ static void sp_shape_snappoints(SPItem const *item, bool const target, SnapPoint bool c2 = snapprefs->getSnapSmoothNodes() && (nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM); if (c1 || c2) { - type = target ? int(Inkscape::SNAPTARGET_NODE_CUSP) : int(Inkscape::SNAPSOURCE_NODE_CUSP); - p.push_back(std::make_pair(curve_it1->finalPoint() * i2d, type)); + p.push_back(Inkscape::SnapCandidatePoint(curve_it1->finalPoint() * i2d, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } - // Consider midpoints of line segments for snapping - if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) - if (Geom::LineSegment const* line_segment = dynamic_cast<Geom::LineSegment const*>(&(*curve_it1))) { - type = target ? int(Inkscape::SNAPTARGET_LINE_MIDPOINT) : int(Inkscape::SNAPSOURCE_LINE_MIDPOINT); - p.push_back(std::make_pair(Geom::middle_point(*line_segment) * i2d, type)); - } - } + // Consider midpoints of line segments for snapping + if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping) + if (Geom::LineSegment const* line_segment = dynamic_cast<Geom::LineSegment const*>(&(*curve_it1))) { + p.push_back(Inkscape::SnapCandidatePoint(Geom::middle_point(*line_segment) * i2d, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + } + } ++curve_it1; ++curve_it2; @@ -1226,8 +1220,7 @@ static void sp_shape_snappoints(SPItem const *item, bool const target, SnapPoint if (cs.size() > 0) { // There might be multiple intersections... for (Geom::Crossings::const_iterator i = cs.begin(); i != cs.end(); i++) { Geom::Point p_ix = (*path_it).pointAt((*i).ta); - type = target ? int(Inkscape::SNAPTARGET_PATH_INTERSECTION) : int(Inkscape::SNAPSOURCE_PATH_INTERSECTION); - p.push_back(std::make_pair(p_ix * i2d, type)); + p.push_back(Inkscape::SnapCandidatePoint(p_ix * i2d, Inkscape::SNAPSOURCE_PATH_INTERSECTION, Inkscape::SNAPTARGET_PATH_INTERSECTION)); } } } diff --git a/src/sp-spiral.cpp b/src/sp-spiral.cpp index 629715332..11e84d9b2 100644 --- a/src/sp-spiral.cpp +++ b/src/sp-spiral.cpp @@ -37,7 +37,7 @@ static void sp_spiral_set (SPObject *object, unsigned int key, const gchar *valu static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags); static gchar * sp_spiral_description (SPItem * item); -static void sp_spiral_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_spiral_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_spiral_set_shape (SPShape *shape); static void sp_spiral_update_patheffect (SPLPEItem *lpeitem, bool write); @@ -52,24 +52,24 @@ static SPShapeClass *parent_class; GType sp_spiral_get_type (void) { - static GType spiral_type = 0; - - if (!spiral_type) { - GTypeInfo spiral_info = { - sizeof (SPSpiralClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) sp_spiral_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (SPSpiral), - 16, /* n_preallocs */ - (GInstanceInitFunc) sp_spiral_init, - NULL, /* value_table */ - }; - spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0); - } - return spiral_type; + static GType spiral_type = 0; + + if (!spiral_type) { + GTypeInfo spiral_info = { + sizeof (SPSpiralClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_spiral_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPSpiral), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_spiral_init, + NULL, /* value_table */ + }; + spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0); + } + return spiral_type; } /** @@ -78,27 +78,27 @@ sp_spiral_get_type (void) static void sp_spiral_class_init (SPSpiralClass *klass) { - GObjectClass * gobject_class; - SPObjectClass * sp_object_class; - SPItemClass * item_class; - SPLPEItemClass * lpe_item_class; - SPShapeClass *shape_class; + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + SPLPEItemClass * lpe_item_class; + SPShapeClass *shape_class; - gobject_class = (GObjectClass *) klass; - sp_object_class = (SPObjectClass *) klass; - item_class = (SPItemClass *) klass; - lpe_item_class = (SPLPEItemClass *) klass; - shape_class = (SPShapeClass *) klass; + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + lpe_item_class = (SPLPEItemClass *) klass; + shape_class = (SPShapeClass *) klass; - parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); - sp_object_class->build = sp_spiral_build; - sp_object_class->write = sp_spiral_write; - sp_object_class->set = sp_spiral_set; - sp_object_class->update = sp_spiral_update; + sp_object_class->build = sp_spiral_build; + sp_object_class->write = sp_spiral_write; + sp_object_class->set = sp_spiral_set; + sp_object_class->update = sp_spiral_update; - item_class->description = sp_spiral_description; - item_class->snappoints = sp_spiral_snappoints; + item_class->description = sp_spiral_description; + item_class->snappoints = sp_spiral_snappoints; lpe_item_class->update_patheffect = sp_spiral_update_patheffect; @@ -111,13 +111,13 @@ sp_spiral_class_init (SPSpiralClass *klass) static void sp_spiral_init (SPSpiral * spiral) { - spiral->cx = 0.0; - spiral->cy = 0.0; - spiral->exp = 1.0; - spiral->revo = 3.0; - spiral->rad = 1.0; - spiral->arg = 0.0; - spiral->t0 = 0.0; + spiral->cx = 0.0; + spiral->cy = 0.0; + spiral->exp = 1.0; + spiral->revo = 3.0; + spiral->rad = 1.0; + spiral->arg = 0.0; + spiral->t0 = 0.0; } /** @@ -126,16 +126,16 @@ sp_spiral_init (SPSpiral * spiral) static void sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) { - if (((SPObjectClass *) parent_class)->build) - ((SPObjectClass *) parent_class)->build (object, document, repr); - - sp_object_read_attr (object, "sodipodi:cx"); - sp_object_read_attr (object, "sodipodi:cy"); - sp_object_read_attr (object, "sodipodi:expansion"); - sp_object_read_attr (object, "sodipodi:revolution"); - sp_object_read_attr (object, "sodipodi:radius"); - sp_object_read_attr (object, "sodipodi:argument"); - sp_object_read_attr (object, "sodipodi:t0"); + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "sodipodi:cx"); + sp_object_read_attr (object, "sodipodi:cy"); + sp_object_read_attr (object, "sodipodi:expansion"); + sp_object_read_attr (object, "sodipodi:revolution"); + sp_object_read_attr (object, "sodipodi:radius"); + sp_object_read_attr (object, "sodipodi:argument"); + sp_object_read_attr (object, "sodipodi:t0"); } /** @@ -144,25 +144,25 @@ sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * static Inkscape::XML::Node * sp_spiral_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { - SPSpiral *spiral = SP_SPIRAL (object); - - if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { - repr = xml_doc->createElement("svg:path"); - } - - if (flags & SP_OBJECT_WRITE_EXT) { - /* Fixme: we may replace these attributes by - * sodipodi:spiral="cx cy exp revo rad arg t0" - */ - repr->setAttribute("sodipodi:type", "spiral"); - sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx); - sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy); - sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp); - sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo); - sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad); - sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg); - sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0); - } + SPSpiral *spiral = SP_SPIRAL (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + /* Fixme: we may replace these attributes by + * sodipodi:spiral="cx cy exp revo rad arg t0" + */ + repr->setAttribute("sodipodi:type", "spiral"); + sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx); + sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy); + sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp); + sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo); + sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad); + sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg); + sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0); + } // make sure the curve is rebuilt with all up-to-date parameters sp_spiral_set_shape ((SPShape *) spiral); @@ -190,61 +190,61 @@ sp_spiral_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::X static void sp_spiral_set (SPObject *object, unsigned int key, const gchar *value) { - SPSpiral *spiral; - SPShape *shape; - - spiral = SP_SPIRAL (object); - shape = SP_SHAPE (object); - - /// \todo fixme: we should really collect updates - switch (key) { - case SP_ATTR_SODIPODI_CX: - if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) { - spiral->cx = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_CY: - if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) { - spiral->cy = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_EXPANSION: - if (value) { - /** \todo + SPSpiral *spiral; + SPShape *shape; + + spiral = SP_SPIRAL (object); + shape = SP_SHAPE (object); + + /// \todo fixme: we should really collect updates + switch (key) { + case SP_ATTR_SODIPODI_CX: + if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) { + spiral->cx = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CY: + if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) { + spiral->cy = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_EXPANSION: + if (value) { + /** \todo * FIXME: check that value looks like a (finite) * number. Create a routine that uses strtod, and * accepts a default value (if strtod finds an error). * N.B. atof/sscanf/strtod consider "nan" and "inf" * to be valid numbers. */ - spiral->exp = g_ascii_strtod (value, NULL); - spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0); - } else { - spiral->exp = 1.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_REVOLUTION: - if (value) { - spiral->revo = g_ascii_strtod (value, NULL); - spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0); - } else { - spiral->revo = 3.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_RADIUS: - if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) { - spiral->rad = MAX (spiral->rad, 0.001); - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_ARGUMENT: - if (value) { - spiral->arg = g_ascii_strtod (value, NULL); - /** \todo + spiral->exp = g_ascii_strtod (value, NULL); + spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0); + } else { + spiral->exp = 1.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_REVOLUTION: + if (value) { + spiral->revo = g_ascii_strtod (value, NULL); + spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0); + } else { + spiral->revo = 3.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_RADIUS: + if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) { + spiral->rad = MAX (spiral->rad, 0.001); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_ARGUMENT: + if (value) { + spiral->arg = g_ascii_strtod (value, NULL); + /** \todo * FIXME: We still need some bounds on arg, for * numerical reasons. E.g., we don't want inf or NaN, * nor near-infinite numbers. I'm inclined to take @@ -252,32 +252,32 @@ sp_spiral_set (SPObject *object, unsigned int key, const gchar *value) * which use atan2 - revo*2*pi, which typically * results in very negative arg. */ - } else { - spiral->arg = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_T0: - if (value) { - spiral->t0 = g_ascii_strtod (value, NULL); - spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999); - /** \todo + } else { + spiral->arg = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_T0: + if (value) { + spiral->t0 = g_ascii_strtod (value, NULL); + spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999); + /** \todo * Have shared constants for the allowable bounds for * attributes. There was a bug here where we used -1.0 * as the minimum (which leads to NaN via, e.g., * pow(-1.0, 0.5); see sp_spiral_get_xy for * requirements. */ - } else { - spiral->t0 = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - default: - if (((SPObjectClass *) parent_class)->set) - ((SPObjectClass *) parent_class)->set (object, key, value); - break; - } + } else { + spiral->t0 = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } } /** @@ -286,12 +286,12 @@ sp_spiral_set (SPObject *object, unsigned int key, const gchar *value) static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags) { - if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { - sp_shape_set_shape ((SPShape *) object); - } + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_shape_set_shape ((SPShape *) object); + } - if (((SPObjectClass *) parent_class)->update) - ((SPObjectClass *) parent_class)->update (object, ctx, flags); + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); } static void @@ -320,9 +320,9 @@ sp_spiral_update_patheffect(SPLPEItem *lpeitem, bool write) static gchar * sp_spiral_description (SPItem * item) { - // TRANSLATORS: since turn count isn't an integer, please adjust the - // string as needed to deal with an localized plural forms. - return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo); + // TRANSLATORS: since turn count isn't an integer, please adjust the + // string as needed to deal with an localized plural forms. + return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo); } @@ -335,34 +335,34 @@ sp_spiral_description (SPItem * item) **/ static void sp_spiral_fit_and_draw (SPSpiral const *spiral, - SPCurve *c, - double dstep, - Geom::Point darray[], - Geom::Point const &hat1, - Geom::Point &hat2, - double *t) + SPCurve *c, + double dstep, + Geom::Point darray[], + Geom::Point const &hat1, + Geom::Point &hat2, + double *t) { #define BEZIER_SIZE 4 #define FITTING_MAX_BEZIERS 4 #define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS) - g_assert (dstep > 0); - g_assert (is_unit_vector (hat1)); - - Geom::Point bezier[BEZIER_LENGTH]; - double d; - int depth, i; - - for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) { - darray[i] = sp_spiral_get_xy(spiral, d); - - /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with - the same value, which upsets chord_length_parameterize.) */ - if ((i != 0) - && (darray[i] == darray[i - 1]) - && (d < 1.0)) { - i--; - d += dstep; - /** We mustn't increase dstep for subsequent values of + g_assert (dstep > 0); + g_assert (is_unit_vector (hat1)); + + Geom::Point bezier[BEZIER_LENGTH]; + double d; + int depth, i; + + for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) { + darray[i] = sp_spiral_get_xy(spiral, d); + + /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with + the same value, which upsets chord_length_parameterize.) */ + if ((i != 0) + && (darray[i] == darray[i - 1]) + && (d < 1.0)) { + i--; + d += dstep; + /** We mustn't increase dstep for subsequent values of * i: for large spiral.exp values, rate of growth * increases very rapidly. */ @@ -378,48 +378,48 @@ sp_spiral_fit_and_draw (SPSpiral const *spiral, * value for next iteration to avoid the problem * mentioned above. */ - } - } + } + } - double const next_t = d - 2 * dstep; - /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */ + double const next_t = d - 2 * dstep; + /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */ - hat2 = -sp_spiral_get_tangent (spiral, next_t); + hat2 = -sp_spiral_get_tangent (spiral, next_t); - /** \todo + /** \todo * We should use better algorithm to specify maximum error. */ - depth = Geom::bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE, - hat1, hat2, - SPIRAL_TOLERANCE*SPIRAL_TOLERANCE, - FITTING_MAX_BEZIERS); - g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier))); + depth = Geom::bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE, + hat1, hat2, + SPIRAL_TOLERANCE*SPIRAL_TOLERANCE, + FITTING_MAX_BEZIERS); + g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier))); #ifdef SPIRAL_DEBUG - if (*t == spiral->t0 || *t == 1.0) - g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n", - debug_state, depth, dstep, spiral->t0, *t, spiral->arg); + if (*t == spiral->t0 || *t == 1.0) + g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n", + debug_state, depth, dstep, spiral->t0, *t, spiral->arg); #endif - if (depth != -1) { - for (i = 0; i < 4*depth; i += 4) { - c->curveto(bezier[i + 1], - bezier[i + 2], - bezier[i + 3]); - } - } else { + if (depth != -1) { + for (i = 0; i < 4*depth; i += 4) { + c->curveto(bezier[i + 1], + bezier[i + 2], + bezier[i + 3]); + } + } else { #ifdef SPIRAL_VERBOSE - g_print ("cant_fit_cubic: t=%g\n", *t); + g_print ("cant_fit_cubic: t=%g\n", *t); #endif - for (i = 1; i < SAMPLE_SIZE; i++) - c->lineto(darray[i]); - } - *t = next_t; - g_assert (is_unit_vector (hat2)); + for (i = 1; i < SAMPLE_SIZE; i++) + c->lineto(darray[i]); + } + *t = next_t; + g_assert (is_unit_vector (hat2)); } static void sp_spiral_set_shape (SPShape *shape) { - SPSpiral *spiral = SP_SPIRAL(shape); + SPSpiral *spiral = SP_SPIRAL(shape); if (sp_lpe_item_has_broken_path_effect(SP_LPE_ITEM(shape))) { g_warning ("The spiral shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as spiral will remove the bad LPE"); @@ -433,40 +433,40 @@ sp_spiral_set_shape (SPShape *shape) return; } - Geom::Point darray[SAMPLE_SIZE + 1]; - double t; + Geom::Point darray[SAMPLE_SIZE + 1]; + double t; - SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG); + SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG); - SPCurve *c = new SPCurve (); + SPCurve *c = new SPCurve (); #ifdef SPIRAL_VERBOSE - g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n", - spiral->cx, - spiral->cy, - spiral->exp, - spiral->revo, - spiral->rad, - spiral->arg, - spiral->t0); + g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n", + spiral->cx, + spiral->cy, + spiral->exp, + spiral->revo, + spiral->rad, + spiral->arg, + spiral->t0); #endif - /* Initial moveto. */ - c->moveto(sp_spiral_get_xy(spiral, spiral->t0)); + /* Initial moveto. */ + c->moveto(sp_spiral_get_xy(spiral, spiral->t0)); - double const tstep = SAMPLE_STEP / spiral->revo; - double const dstep = tstep / (SAMPLE_SIZE - 1); + double const tstep = SAMPLE_STEP / spiral->revo; + double const dstep = tstep / (SAMPLE_SIZE - 1); - Geom::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0); - Geom::Point hat2; - for (t = spiral->t0; t < (1.0 - tstep);) { - sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t); + Geom::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0); + Geom::Point hat2; + for (t = spiral->t0; t < (1.0 - tstep);) { + sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t); - hat1 = -hat2; - } - if ((1.0 - t) > SP_EPSILON) - sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0), - darray, hat1, hat2, &t); + hat1 = -hat2; + } + if ((1.0 - t) > SP_EPSILON) + sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0), + darray, hat1, hat2, &t); /* Reset the shape'scurve to the "original_curve" * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ @@ -487,59 +487,58 @@ sp_spiral_set_shape (SPShape *shape) */ void sp_spiral_position_set (SPSpiral *spiral, - gdouble cx, - gdouble cy, - gdouble exp, - gdouble revo, - gdouble rad, - gdouble arg, - gdouble t0) + gdouble cx, + gdouble cy, + gdouble exp, + gdouble revo, + gdouble rad, + gdouble arg, + gdouble t0) { - g_return_if_fail (spiral != NULL); - g_return_if_fail (SP_IS_SPIRAL (spiral)); + g_return_if_fail (spiral != NULL); + g_return_if_fail (SP_IS_SPIRAL (spiral)); - /** \todo + /** \todo * Consider applying CLAMP or adding in-bounds assertions for * some of these parameters. */ - spiral->cx = cx; - spiral->cy = cy; - spiral->exp = exp; - spiral->revo = revo; - spiral->rad = MAX (rad, 0.0); - spiral->arg = arg; - spiral->t0 = CLAMP(t0, 0.0, 0.999); - - ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + spiral->cx = cx; + spiral->cy = cy; + spiral->exp = exp; + spiral->revo = revo; + spiral->rad = MAX (rad, 0.0); + spiral->arg = arg; + spiral->t0 = CLAMP(t0, 0.0, 0.999); + + ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } /** * Virtual snappoints callback. */ -static void sp_spiral_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_spiral_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { - // We will determine the spiral's midpoint ourselves, instead of trusting on the base class - // Therefore setSnapObjectMidpoints() is set to false temporarily - Inkscape::SnapPreferences local_snapprefs = *snapprefs; - local_snapprefs.setSnapObjectMidpoints(false); - - if (((SPItemClass *) parent_class)->snappoints) { - ((SPItemClass *) parent_class)->snappoints (item, target, p, &local_snapprefs); - } - - // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes - if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { - return; - } - - if (snapprefs->getSnapObjectMidpoints()) { - Geom::Matrix const i2d (sp_item_i2d_affine (item)); - SPSpiral *spiral = SP_SPIRAL(item); - int type = target ? int(Inkscape::SNAPTARGET_OBJECT_MIDPOINT) : int(Inkscape::SNAPSOURCE_OBJECT_MIDPOINT); - p.push_back(std::make_pair(Geom::Point(spiral->cx, spiral->cy) * i2d, type)); - // This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set - // in the object snapper. In that case we will get a duplicate! - } + // We will determine the spiral's midpoint ourselves, instead of trusting on the base class + // Therefore setSnapObjectMidpoints() is set to false temporarily + Inkscape::SnapPreferences local_snapprefs = *snapprefs; + local_snapprefs.setSnapObjectMidpoints(false); + + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p, &local_snapprefs); + } + + // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes + if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { + return; + } + + if (snapprefs->getSnapObjectMidpoints()) { + Geom::Matrix const i2d (sp_item_i2d_affine (item)); + SPSpiral *spiral = SP_SPIRAL(item); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(spiral->cx, spiral->cy) * i2d, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + // This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set + // in the object snapper. In that case we will get a duplicate! + } } /** @@ -552,19 +551,19 @@ static void sp_spiral_snappoints(SPItem const *item, bool const target, SnapPoin */ Geom::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t) { - g_assert (spiral != NULL); - g_assert (SP_IS_SPIRAL(spiral)); - g_assert (spiral->exp >= 0.0); - /* Otherwise we get NaN for t==0. */ - g_assert (spiral->exp <= 1000.0); - /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */ - g_assert (t >= 0.0); - /* Any callers passing -ve t will have a bug for non-integral values of exp. */ - - double const rad = spiral->rad * pow(t, (double) spiral->exp); - double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; - - return Geom::Point(rad * cos (arg) + spiral->cx, + g_assert (spiral != NULL); + g_assert (SP_IS_SPIRAL(spiral)); + g_assert (spiral->exp >= 0.0); + /* Otherwise we get NaN for t==0. */ + g_assert (spiral->exp <= 1000.0); + /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */ + g_assert (t >= 0.0); + /* Any callers passing -ve t will have a bug for non-integral values of exp. */ + + double const rad = spiral->rad * pow(t, (double) spiral->exp); + double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; + + return Geom::Point(rad * cos (arg) + spiral->cx, rad * sin (arg) + spiral->cy); } @@ -581,58 +580,58 @@ Geom::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t) static Geom::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t) { - Geom::Point ret(1.0, 0.0); - g_return_val_if_fail (( ( spiral != NULL ) - && SP_IS_SPIRAL(spiral) ), - ret); - g_assert (t >= 0.0); - g_assert (spiral->exp >= 0.0); - /* See above for comments on these assertions. */ - - double const t_scaled = 2.0 * M_PI * spiral->revo * t; - double const arg = t_scaled + spiral->arg; - double const s = sin (arg); - double const c = cos (arg); - - if (spiral->exp == 0.0) { - ret = Geom::Point(-s, c); - } else if (t_scaled == 0.0) { - ret = Geom::Point(c, s); - } else { - Geom::Point unrotated(spiral->exp, t_scaled); - double const s_len = L2 (unrotated); - g_assert (s_len != 0); - /** \todo + Geom::Point ret(1.0, 0.0); + g_return_val_if_fail (( ( spiral != NULL ) + && SP_IS_SPIRAL(spiral) ), + ret); + g_assert (t >= 0.0); + g_assert (spiral->exp >= 0.0); + /* See above for comments on these assertions. */ + + double const t_scaled = 2.0 * M_PI * spiral->revo * t; + double const arg = t_scaled + spiral->arg; + double const s = sin (arg); + double const c = cos (arg); + + if (spiral->exp == 0.0) { + ret = Geom::Point(-s, c); + } else if (t_scaled == 0.0) { + ret = Geom::Point(c, s); + } else { + Geom::Point unrotated(spiral->exp, t_scaled); + double const s_len = L2 (unrotated); + g_assert (s_len != 0); + /** \todo * Check that this isn't being too hopeful of the hypot * function. E.g. test with numbers around 2**-1070 * (denormalized numbers), preferably on a few different * platforms. However, njh says that the usual implementation * does handle both very big and very small numbers. */ - unrotated /= s_len; + unrotated /= s_len; - /* ret = spiral->exp * (c, s) + t_scaled * (-s, c); - alternatively ret = (spiral->exp, t_scaled) * (( c, s), - (-s, c)).*/ - ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)), + /* ret = spiral->exp * (c, s) + t_scaled * (-s, c); + alternatively ret = (spiral->exp, t_scaled) * (( c, s), + (-s, c)).*/ + ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)), dot(unrotated, Geom::Point(s, c))); - /* ret should already be approximately normalized: the - matrix ((c, -s), (s, c)) is orthogonal (it just - rotates by arg), and unrotated has been normalized, - so ret is already of unit length other than numerical - error in the above matrix multiplication. */ + /* ret should already be approximately normalized: the + matrix ((c, -s), (s, c)) is orthogonal (it just + rotates by arg), and unrotated has been normalized, + so ret is already of unit length other than numerical + error in the above matrix multiplication. */ - /** \todo + /** \todo * I haven't checked how important it is for ret to be very * near unit length; we could get rid of the below. */ - ret.normalize(); - /* Proof that ret length is non-zero: see above. (Should be near 1.) */ - } + ret.normalize(); + /* Proof that ret length is non-zero: see above. (Should be near 1.) */ + } - g_assert (is_unit_vector (ret)); - return ret; + g_assert (is_unit_vector (ret)); + return ret; } /** @@ -641,13 +640,13 @@ sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t) void sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg) { - g_return_if_fail (spiral != NULL); - g_return_if_fail (SP_IS_SPIRAL(spiral)); + g_return_if_fail (spiral != NULL); + g_return_if_fail (SP_IS_SPIRAL(spiral)); - if (rad) - *rad = spiral->rad * pow(t, (double) spiral->exp); - if (arg) - *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; + if (rad) + *rad = spiral->rad * pow(t, (double) spiral->exp); + if (arg) + *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg; } /** @@ -656,19 +655,19 @@ sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *a bool sp_spiral_is_invalid (SPSpiral const *spiral) { - gdouble rad; - - sp_spiral_get_polar (spiral, 0.0, &rad, NULL); - if (rad < 0.0 || rad > SP_HUGE) { - g_print ("rad(t=0)=%g\n", rad); - return TRUE; - } - sp_spiral_get_polar (spiral, 1.0, &rad, NULL); - if (rad < 0.0 || rad > SP_HUGE) { - g_print ("rad(t=1)=%g\n", rad); - return TRUE; - } - return FALSE; + gdouble rad; + + sp_spiral_get_polar (spiral, 0.0, &rad, NULL); + if (rad < 0.0 || rad > SP_HUGE) { + g_print ("rad(t=0)=%g\n", rad); + return TRUE; + } + sp_spiral_get_polar (spiral, 1.0, &rad, NULL); + if (rad < 0.0 || rad > SP_HUGE) { + g_print ("rad(t=1)=%g\n", rad); + return TRUE; + } + return FALSE; } /* diff --git a/src/sp-star.cpp b/src/sp-star.cpp index 9cffd952c..6bced87e8 100644 --- a/src/sp-star.cpp +++ b/src/sp-star.cpp @@ -41,7 +41,7 @@ static void sp_star_set (SPObject *object, unsigned int key, const gchar *value) static void sp_star_update (SPObject *object, SPCtx *ctx, guint flags); static gchar * sp_star_description (SPItem * item); -static void sp_star_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_star_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_star_set_shape (SPShape *shape); static void sp_star_update_patheffect (SPLPEItem *lpeitem, bool write); @@ -51,230 +51,230 @@ static SPShapeClass *parent_class; GType sp_star_get_type (void) { - static GType type = 0; - - if (!type) { - GTypeInfo info = { - sizeof (SPStarClass), - NULL, NULL, - (GClassInitFunc) sp_star_class_init, - NULL, NULL, - sizeof (SPStar), - 16, - (GInstanceInitFunc) sp_star_init, - NULL, /* value_table */ - }; - type = g_type_register_static (SP_TYPE_SHAPE, "SPStar", &info, (GTypeFlags)0); - } - return type; + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof (SPStarClass), + NULL, NULL, + (GClassInitFunc) sp_star_class_init, + NULL, NULL, + sizeof (SPStar), + 16, + (GInstanceInitFunc) sp_star_init, + NULL, /* value_table */ + }; + type = g_type_register_static (SP_TYPE_SHAPE, "SPStar", &info, (GTypeFlags)0); + } + return type; } static void sp_star_class_init (SPStarClass *klass) { - GObjectClass * gobject_class; - SPObjectClass * sp_object_class; - SPItemClass * item_class; - SPLPEItemClass * lpe_item_class; - SPShapeClass * shape_class; + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + SPLPEItemClass * lpe_item_class; + SPShapeClass * shape_class; - gobject_class = (GObjectClass *) klass; - sp_object_class = (SPObjectClass *) klass; - item_class = (SPItemClass *) klass; - lpe_item_class = (SPLPEItemClass *) klass; - shape_class = (SPShapeClass *) klass; + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + lpe_item_class = (SPLPEItemClass *) klass; + shape_class = (SPShapeClass *) klass; - parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); + parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE); - sp_object_class->build = sp_star_build; - sp_object_class->write = sp_star_write; - sp_object_class->set = sp_star_set; - sp_object_class->update = sp_star_update; + sp_object_class->build = sp_star_build; + sp_object_class->write = sp_star_write; + sp_object_class->set = sp_star_set; + sp_object_class->update = sp_star_update; - item_class->description = sp_star_description; - item_class->snappoints = sp_star_snappoints; + item_class->description = sp_star_description; + item_class->snappoints = sp_star_snappoints; lpe_item_class->update_patheffect = sp_star_update_patheffect; - shape_class->set_shape = sp_star_set_shape; + shape_class->set_shape = sp_star_set_shape; } static void sp_star_init (SPStar * star) { - star->sides = 5; - star->center = Geom::Point(0, 0); - star->r[0] = 1.0; - star->r[1] = 0.001; - star->arg[0] = star->arg[1] = 0.0; - star->flatsided = 0; - star->rounded = 0.0; - star->randomized = 0.0; + star->sides = 5; + star->center = Geom::Point(0, 0); + star->r[0] = 1.0; + star->r[1] = 0.001; + star->arg[0] = star->arg[1] = 0.0; + star->flatsided = 0; + star->rounded = 0.0; + star->randomized = 0.0; } static void sp_star_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr) { - if (((SPObjectClass *) parent_class)->build) - ((SPObjectClass *) parent_class)->build (object, document, repr); - - sp_object_read_attr (object, "sodipodi:cx"); - sp_object_read_attr (object, "sodipodi:cy"); - sp_object_read_attr (object, "sodipodi:sides"); - sp_object_read_attr (object, "sodipodi:r1"); - sp_object_read_attr (object, "sodipodi:r2"); - sp_object_read_attr (object, "sodipodi:arg1"); - sp_object_read_attr (object, "sodipodi:arg2"); - sp_object_read_attr (object, "inkscape:flatsided"); - sp_object_read_attr (object, "inkscape:rounded"); - sp_object_read_attr (object, "inkscape:randomized"); + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "sodipodi:cx"); + sp_object_read_attr (object, "sodipodi:cy"); + sp_object_read_attr (object, "sodipodi:sides"); + sp_object_read_attr (object, "sodipodi:r1"); + sp_object_read_attr (object, "sodipodi:r2"); + sp_object_read_attr (object, "sodipodi:arg1"); + sp_object_read_attr (object, "sodipodi:arg2"); + sp_object_read_attr (object, "inkscape:flatsided"); + sp_object_read_attr (object, "inkscape:rounded"); + sp_object_read_attr (object, "inkscape:randomized"); } static Inkscape::XML::Node * sp_star_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { - SPStar *star = SP_STAR (object); - - if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { - repr = xml_doc->createElement("svg:path"); - } - - if (flags & SP_OBJECT_WRITE_EXT) { - repr->setAttribute("sodipodi:type", "star"); - sp_repr_set_int (repr, "sodipodi:sides", star->sides); - sp_repr_set_svg_double(repr, "sodipodi:cx", star->center[Geom::X]); - sp_repr_set_svg_double(repr, "sodipodi:cy", star->center[Geom::Y]); - sp_repr_set_svg_double(repr, "sodipodi:r1", star->r[0]); - sp_repr_set_svg_double(repr, "sodipodi:r2", star->r[1]); - sp_repr_set_svg_double(repr, "sodipodi:arg1", star->arg[0]); - sp_repr_set_svg_double(repr, "sodipodi:arg2", star->arg[1]); - sp_repr_set_boolean (repr, "inkscape:flatsided", star->flatsided); - sp_repr_set_svg_double(repr, "inkscape:rounded", star->rounded); - sp_repr_set_svg_double(repr, "inkscape:randomized", star->randomized); - } + SPStar *star = SP_STAR (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:type", "star"); + sp_repr_set_int (repr, "sodipodi:sides", star->sides); + sp_repr_set_svg_double(repr, "sodipodi:cx", star->center[Geom::X]); + sp_repr_set_svg_double(repr, "sodipodi:cy", star->center[Geom::Y]); + sp_repr_set_svg_double(repr, "sodipodi:r1", star->r[0]); + sp_repr_set_svg_double(repr, "sodipodi:r2", star->r[1]); + sp_repr_set_svg_double(repr, "sodipodi:arg1", star->arg[0]); + sp_repr_set_svg_double(repr, "sodipodi:arg2", star->arg[1]); + sp_repr_set_boolean (repr, "inkscape:flatsided", star->flatsided); + sp_repr_set_svg_double(repr, "inkscape:rounded", star->rounded); + sp_repr_set_svg_double(repr, "inkscape:randomized", star->randomized); + } sp_star_set_shape ((SPShape *) star); char *d = sp_svg_write_path (((SPShape *) star)->curve->get_pathvector()); repr->setAttribute("d", d); g_free (d); - if (((SPObjectClass *) (parent_class))->write) - ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags); + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags); - return repr; + return repr; } static void sp_star_set (SPObject *object, unsigned int key, const gchar *value) { - SVGLength::Unit unit; - - SPStar *star = SP_STAR (object); - - /* fixme: we should really collect updates */ - switch (key) { - case SP_ATTR_SODIPODI_SIDES: - if (value) { - star->sides = atoi (value); - star->sides = NR_CLAMP(star->sides, 3, 1024); - } else { - star->sides = 5; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_CX: - if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[Geom::X]) || - (unit == SVGLength::EM) || - (unit == SVGLength::EX) || - (unit == SVGLength::PERCENT)) { - star->center[Geom::X] = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_CY: - if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[Geom::Y]) || - (unit == SVGLength::EM) || - (unit == SVGLength::EX) || - (unit == SVGLength::PERCENT)) { - star->center[Geom::Y] = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_R1: - if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[0]) || - (unit == SVGLength::EM) || - (unit == SVGLength::EX) || - (unit == SVGLength::PERCENT)) { - star->r[0] = 1.0; - } - /* fixme: Need CLAMP (Lauris) */ - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_R2: - if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[1]) || - (unit == SVGLength::EM) || - (unit == SVGLength::EX) || - (unit == SVGLength::PERCENT)) { - star->r[1] = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - return; - case SP_ATTR_SODIPODI_ARG1: - if (value) { - star->arg[0] = g_ascii_strtod (value, NULL); - } else { - star->arg[0] = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_SODIPODI_ARG2: - if (value) { - star->arg[1] = g_ascii_strtod (value, NULL); - } else { - star->arg[1] = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_INKSCAPE_FLATSIDED: - if (value && !strcmp (value, "true")) - star->flatsided = true; - else star->flatsided = false; - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_INKSCAPE_ROUNDED: - if (value) { - star->rounded = g_ascii_strtod (value, NULL); - } else { - star->rounded = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - case SP_ATTR_INKSCAPE_RANDOMIZED: - if (value) { - star->randomized = g_ascii_strtod (value, NULL); - } else { - star->randomized = 0.0; - } - object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - break; - default: - if (((SPObjectClass *) parent_class)->set) - ((SPObjectClass *) parent_class)->set (object, key, value); - break; - } + SVGLength::Unit unit; + + SPStar *star = SP_STAR (object); + + /* fixme: we should really collect updates */ + switch (key) { + case SP_ATTR_SODIPODI_SIDES: + if (value) { + star->sides = atoi (value); + star->sides = NR_CLAMP(star->sides, 3, 1024); + } else { + star->sides = 5; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CX: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[Geom::X]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->center[Geom::X] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_CY: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->center[Geom::Y]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->center[Geom::Y] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_R1: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[0]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->r[0] = 1.0; + } + /* fixme: Need CLAMP (Lauris) */ + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_R2: + if (!sp_svg_length_read_ldd (value, &unit, NULL, &star->r[1]) || + (unit == SVGLength::EM) || + (unit == SVGLength::EX) || + (unit == SVGLength::PERCENT)) { + star->r[1] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + return; + case SP_ATTR_SODIPODI_ARG1: + if (value) { + star->arg[0] = g_ascii_strtod (value, NULL); + } else { + star->arg[0] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SODIPODI_ARG2: + if (value) { + star->arg[1] = g_ascii_strtod (value, NULL); + } else { + star->arg[1] = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_FLATSIDED: + if (value && !strcmp (value, "true")) + star->flatsided = true; + else star->flatsided = false; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_ROUNDED: + if (value) { + star->rounded = g_ascii_strtod (value, NULL); + } else { + star->rounded = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_RANDOMIZED: + if (value) { + star->randomized = g_ascii_strtod (value, NULL); + } else { + star->randomized = 0.0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set (object, key, value); + break; + } } static void sp_star_update (SPObject *object, SPCtx *ctx, guint flags) { - if (flags & (SP_OBJECT_MODIFIED_FLAG | - SP_OBJECT_STYLE_MODIFIED_FLAG | - SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { - sp_shape_set_shape ((SPShape *) object); - } - - if (((SPObjectClass *) parent_class)->update) - ((SPObjectClass *) parent_class)->update (object, ctx, flags); + if (flags & (SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + sp_shape_set_shape ((SPShape *) object); + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update (object, ctx, flags); } static void @@ -306,13 +306,13 @@ sp_star_description (SPItem *item) // make calls to ngettext because the pluralization may be different // for various numbers >=3. The singular form is used as the index. if (star->flatsided == false ) - return g_strdup_printf (ngettext("<b>Star</b> with %d vertex", - "<b>Star</b> with %d vertices", - star->sides), star->sides); + return g_strdup_printf (ngettext("<b>Star</b> with %d vertex", + "<b>Star</b> with %d vertices", + star->sides), star->sides); else return g_strdup_printf (ngettext("<b>Polygon</b> with %d vertex", - "<b>Polygon</b> with %d vertices", - star->sides), star->sides); + "<b>Polygon</b> with %d vertices", + star->sides), star->sides); } /** @@ -321,7 +321,7 @@ Returns a unit-length vector at 90 degrees to the direction from o to n static Geom::Point rot90_rel (Geom::Point o, Geom::Point n) { - return ((1/Geom::L2(n - o)) * Geom::Point ((n - o)[Geom::Y], (o - n)[Geom::X])); + return ((1/Geom::L2(n - o)) * Geom::Point ((n - o)[Geom::Y], (o - n)[Geom::X])); } /** @@ -333,12 +333,12 @@ Obvious (but acceptable for my purposes) limits to uniqueness: static guint32 point_unique_int (Geom::Point o) { - return ((guint32) - 65536 * - (((int) floor (o[Geom::X] * 64)) % 1024 + ((int) floor (o[Geom::X] * 1024)) % 64) - + - (((int) floor (o[Geom::Y] * 64)) % 1024 + ((int) floor (o[Geom::Y] * 1024)) % 64) - ); + return ((guint32) + 65536 * + (((int) floor (o[Geom::X] * 64)) % 1024 + ((int) floor (o[Geom::X] * 1024)) % 64) + + + (((int) floor (o[Geom::Y] * 64)) % 1024 + ((int) floor (o[Geom::Y] * 1024)) % 64) + ); } /** @@ -349,7 +349,7 @@ i.e. it is guaranteed to go through all integers < 2^32 (see http://random.mat.s static inline guint32 lcg_next(guint32 const prev) { - return (guint32) ( 69069 * prev + 1 ); + return (guint32) ( 69069 * prev + 1 ); } /** @@ -357,68 +357,68 @@ Returns a random number in the range [-0.5, 0.5) from the given seed, stepping t */ static double rnd (guint32 const seed, unsigned steps) { - guint32 lcg = seed; - for (; steps > 0; steps --) - lcg = lcg_next (lcg); + guint32 lcg = seed; + for (; steps > 0; steps --) + lcg = lcg_next (lcg); - return ( lcg / 4294967296. ) - 0.5; + return ( lcg / 4294967296. ) - 0.5; } static Geom::Point sp_star_get_curvepoint (SPStar *star, SPStarPoint point, gint index, bool previ) { - // the point whose neighboring curve handle we're calculating - Geom::Point o = sp_star_get_xy (star, point, index); - - // indices of previous and next points - gint pi = (index > 0)? (index - 1) : (star->sides - 1); - gint ni = (index < star->sides - 1)? (index + 1) : 0; - - // the other point type - SPStarPoint other = (point == SP_STAR_POINT_KNOT2? SP_STAR_POINT_KNOT1 : SP_STAR_POINT_KNOT2); - - // the neighbors of o; depending on flatsided, they're either the same type (polygon) or the other type (star) - Geom::Point prev = (star->flatsided? sp_star_get_xy (star, point, pi) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT2? index : pi)); - Geom::Point next = (star->flatsided? sp_star_get_xy (star, point, ni) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT1? index : ni)); - - // prev-next midpoint - Geom::Point mid = 0.5 * (prev + next); - - // point to which we direct the bissector of the curve handles; - // it's far enough outside the star on the perpendicular to prev-next through mid - Geom::Point biss = mid + 100000 * rot90_rel (mid, next); - - // lengths of vectors to prev and next - gdouble prev_len = Geom::L2 (prev - o); - gdouble next_len = Geom::L2 (next - o); - - // unit-length vector perpendicular to o-biss - Geom::Point rot = rot90_rel (o, biss); - - // multiply rot by star->rounded coefficient and the distance to the star point; flip for next - Geom::Point ret; - if (previ) { - ret = (star->rounded * prev_len) * rot; - } else { - ret = (star->rounded * next_len * -1) * rot; - } - - if (star->randomized == 0) { - // add the vector to o to get the final curvepoint - return o + ret; - } else { - // the seed corresponding to the exact point - guint32 seed = point_unique_int (o); - - // randomly rotate (by step 3 from the seed) and scale (by step 4) the vector - ret = ret * Geom::Matrix (Geom::Rotate (star->randomized * M_PI * rnd (seed, 3))); - ret *= ( 1 + star->randomized * rnd (seed, 4)); - - // the randomized corner point - Geom::Point o_randomized = sp_star_get_xy (star, point, index, true); - - return o_randomized + ret; - } + // the point whose neighboring curve handle we're calculating + Geom::Point o = sp_star_get_xy (star, point, index); + + // indices of previous and next points + gint pi = (index > 0)? (index - 1) : (star->sides - 1); + gint ni = (index < star->sides - 1)? (index + 1) : 0; + + // the other point type + SPStarPoint other = (point == SP_STAR_POINT_KNOT2? SP_STAR_POINT_KNOT1 : SP_STAR_POINT_KNOT2); + + // the neighbors of o; depending on flatsided, they're either the same type (polygon) or the other type (star) + Geom::Point prev = (star->flatsided? sp_star_get_xy (star, point, pi) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT2? index : pi)); + Geom::Point next = (star->flatsided? sp_star_get_xy (star, point, ni) : sp_star_get_xy (star, other, point == SP_STAR_POINT_KNOT1? index : ni)); + + // prev-next midpoint + Geom::Point mid = 0.5 * (prev + next); + + // point to which we direct the bissector of the curve handles; + // it's far enough outside the star on the perpendicular to prev-next through mid + Geom::Point biss = mid + 100000 * rot90_rel (mid, next); + + // lengths of vectors to prev and next + gdouble prev_len = Geom::L2 (prev - o); + gdouble next_len = Geom::L2 (next - o); + + // unit-length vector perpendicular to o-biss + Geom::Point rot = rot90_rel (o, biss); + + // multiply rot by star->rounded coefficient and the distance to the star point; flip for next + Geom::Point ret; + if (previ) { + ret = (star->rounded * prev_len) * rot; + } else { + ret = (star->rounded * next_len * -1) * rot; + } + + if (star->randomized == 0) { + // add the vector to o to get the final curvepoint + return o + ret; + } else { + // the seed corresponding to the exact point + guint32 seed = point_unique_int (o); + + // randomly rotate (by step 3 from the seed) and scale (by step 4) the vector + ret = ret * Geom::Matrix (Geom::Rotate (star->randomized * M_PI * rnd (seed, 3))); + ret *= ( 1 + star->randomized * rnd (seed, 4)); + + // the randomized corner point + Geom::Point o_randomized = sp_star_get_xy (star, point, index, true); + + return o_randomized + ret; + } } @@ -428,7 +428,7 @@ sp_star_get_curvepoint (SPStar *star, SPStarPoint point, gint index, bool previ) static void sp_star_set_shape (SPShape *shape) { - SPStar *star = SP_STAR (shape); + SPStar *star = SP_STAR (shape); // perhaps we should convert all our shapes into LPEs without source path // and with knotholders for parameters, then this situation will be handled automatically @@ -445,67 +445,67 @@ sp_star_set_shape (SPShape *shape) return; } - SPCurve *c = new SPCurve (); - - gint sides = star->sides; - bool not_rounded = (fabs (star->rounded) < 1e-4); - - // note that we pass randomized=true to sp_star_get_xy, because the curve must be randomized; - // other places that call that function (e.g. the knotholder) need the exact point - - // draw 1st segment - c->moveto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); - if (star->flatsided == false) { - if (not_rounded) { - c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); - } else { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, 0, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); - } - } - - // draw all middle segments - for (gint i = 1; i < sides; i++) { - if (not_rounded) { - c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); - } else { - if (star->flatsided == false) { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i - 1, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); - } else { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i - 1, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); - } - } - if (star->flatsided == false) { - - if (not_rounded) { + SPCurve *c = new SPCurve (); + + gint sides = star->sides; + bool not_rounded = (fabs (star->rounded) < 1e-4); + + // note that we pass randomized=true to sp_star_get_xy, because the curve must be randomized; + // other places that call that function (e.g. the knotholder) need the exact point + + // draw 1st segment + c->moveto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + if (star->flatsided == false) { + if (not_rounded) { + c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); + } else { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT2, 0, true)); + } + } + + // draw all middle segments + for (gint i = 1; i < sides; i++) { + if (not_rounded) { + c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } else { + if (star->flatsided == false) { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } else { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, i, true)); + } + } + if (star->flatsided == false) { + + if (not_rounded) { c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT2, i, true)); - } else { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT2, i, true)); - } - } - } - - // draw last segment - if (not_rounded) { - c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); - } else { - if (star->flatsided == false) { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, sides - 1, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); - } else { - c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, sides - 1, NEXT), - sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), - sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); - } - } + } else { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, i, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, i, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT2, i, true)); + } + } + } + + // draw last segment + if (not_rounded) { + c->lineto(sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } else { + if (star->flatsided == false) { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT2, sides - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } else { + c->curveto(sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, sides - 1, NEXT), + sp_star_get_curvepoint (star, SP_STAR_POINT_KNOT1, 0, PREV), + sp_star_get_xy (star, SP_STAR_POINT_KNOT1, 0, true)); + } + } c->closepath(); @@ -526,46 +526,45 @@ sp_star_set_shape (SPShape *shape) void sp_star_position_set (SPStar *star, gint sides, Geom::Point center, gdouble r1, gdouble r2, gdouble arg1, gdouble arg2, bool isflat, double rounded, double randomized) { - g_return_if_fail (star != NULL); - g_return_if_fail (SP_IS_STAR (star)); - - star->sides = NR_CLAMP(sides, 3, 1024); - star->center = center; - star->r[0] = MAX (r1, 0.001); - if (isflat == false) { - star->r[1] = NR_CLAMP(r2, 0.0, star->r[0]); - } else { - star->r[1] = NR_CLAMP( r1*cos(M_PI/sides) ,0.0, star->r[0] ); - } - star->arg[0] = arg1; - star->arg[1] = arg2; - star->flatsided = isflat; - star->rounded = rounded; - star->randomized = randomized; - SP_OBJECT(star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + g_return_if_fail (star != NULL); + g_return_if_fail (SP_IS_STAR (star)); + + star->sides = NR_CLAMP(sides, 3, 1024); + star->center = center; + star->r[0] = MAX (r1, 0.001); + if (isflat == false) { + star->r[1] = NR_CLAMP(r2, 0.0, star->r[0]); + } else { + star->r[1] = NR_CLAMP( r1*cos(M_PI/sides) ,0.0, star->r[0] ); + } + star->arg[0] = arg1; + star->arg[1] = arg2; + star->flatsided = isflat; + star->rounded = rounded; + star->randomized = randomized; + SP_OBJECT(star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } -static void sp_star_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +static void sp_star_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { - // We will determine the star's midpoint ourselves, instead of trusting on the base class - // Therefore setSnapObjectMidpoints() is set to false temporarily - Inkscape::SnapPreferences local_snapprefs = *snapprefs; - local_snapprefs.setSnapObjectMidpoints(false); - - if (((SPItemClass *) parent_class)->snappoints) { - ((SPItemClass *) parent_class)->snappoints (item, target, p, &local_snapprefs); - } - - // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes - if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { - return; - } - - if (snapprefs->getSnapObjectMidpoints()) { - Geom::Matrix const i2d (sp_item_i2d_affine (item)); - int type = target ? int(Inkscape::SNAPTARGET_OBJECT_MIDPOINT) : int(Inkscape::SNAPSOURCE_OBJECT_MIDPOINT); - p.push_back(std::make_pair(SP_STAR(item)->center * i2d, type)); - } + // We will determine the star's midpoint ourselves, instead of trusting on the base class + // Therefore setSnapObjectMidpoints() is set to false temporarily + Inkscape::SnapPreferences local_snapprefs = *snapprefs; + local_snapprefs.setSnapObjectMidpoints(false); + + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p, &local_snapprefs); + } + + // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes + if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) { + return; + } + + if (snapprefs->getSnapObjectMidpoints()) { + Geom::Matrix const i2d (sp_item_i2d_affine (item)); + p.push_back(Inkscape::SnapCandidatePoint(SP_STAR(item)->center * i2d,Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + } } /** @@ -582,26 +581,26 @@ static void sp_star_snappoints(SPItem const *item, bool const target, SnapPoints Geom::Point sp_star_get_xy (SPStar *star, SPStarPoint point, gint index, bool randomized) { - gdouble darg = 2.0 * M_PI / (double) star->sides; - - double arg = star->arg[point]; - arg += index * darg; - - Geom::Point xy = star->r[point] * Geom::Point(cos(arg), sin(arg)) + star->center; - - if (!randomized || star->randomized == 0) { - // return the exact point - return xy; - } else { // randomize the point - // find out the seed, unique for this point so that randomization is the same so long as the original point is stationary - guint32 seed = point_unique_int (xy); - // the full range (corresponding to star->randomized == 1.0) is equal to the star's diameter - double range = 2 * MAX (star->r[0], star->r[1]); - // find out the random displacement; x is controlled by step 1 from the seed, y by the step 2 - Geom::Point shift (star->randomized * range * rnd (seed, 1), star->randomized * range * rnd (seed, 2)); - // add the shift to the exact point - return xy + shift; - } + gdouble darg = 2.0 * M_PI / (double) star->sides; + + double arg = star->arg[point]; + arg += index * darg; + + Geom::Point xy = star->r[point] * Geom::Point(cos(arg), sin(arg)) + star->center; + + if (!randomized || star->randomized == 0) { + // return the exact point + return xy; + } else { // randomize the point + // find out the seed, unique for this point so that randomization is the same so long as the original point is stationary + guint32 seed = point_unique_int (xy); + // the full range (corresponding to star->randomized == 1.0) is equal to the star's diameter + double range = 2 * MAX (star->r[0], star->r[1]); + // find out the random displacement; x is controlled by step 1 from the seed, y by the step 2 + Geom::Point shift (star->randomized * range * rnd (seed, 1), star->randomized * range * rnd (seed, 2)); + // add the shift to the exact point + return xy + shift; + } } /* diff --git a/src/sp-text.cpp b/src/sp-text.cpp index 0d3fd791b..55da52f1a 100644 --- a/src/sp-text.cpp +++ b/src/sp-text.cpp @@ -74,7 +74,7 @@ static void sp_text_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &t static NRArenaItem *sp_text_show (SPItem *item, NRArena *arena, unsigned key, unsigned flags); static void sp_text_hide (SPItem *item, unsigned key); static char *sp_text_description (SPItem *item); -static void sp_text_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_text_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static Geom::Matrix sp_text_set_transform(SPItem *item, Geom::Matrix const &xform); static void sp_text_print (SPItem *item, SPPrintContext *gpc); @@ -421,20 +421,29 @@ sp_text_description(SPItem *item) GString *xs = SP_PX_TO_METRIC_STRING(style->font_size.computed, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->getDefaultMetric()); + char *trunc = ""; + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) item); + if (layout && layout->inputTruncated()) { + trunc = _(" [truncated]"); + } + char *ret = ( SP_IS_TEXT_TEXTPATH(item) - ? g_strdup_printf(_("<b>Text on path</b> (%s, %s)"), n, xs->str) - : g_strdup_printf(_("<b>Text</b> (%s, %s)"), n, xs->str) ); + ? g_strdup_printf(_("<b>Text on path</b>%s (%s, %s)"), trunc, n, xs->str) + : g_strdup_printf(_("<b>Text</b>%s (%s, %s)"), trunc, n, xs->str) ); g_free(n); return ret; } -static void sp_text_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const */*snapprefs*/) +static void sp_text_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/) { - // the baseline anchor of the first char + // Choose a point on the baseline for snapping from or to, with the horizontal position + // of this point depending on the text alignment (left vs. right) Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) item); - if(layout != NULL) { - int type = target ? int(Inkscape::SNAPTARGET_TEXT_BASELINE) : int(Inkscape::SNAPSOURCE_TEXT_BASELINE); - p.push_back(std::make_pair(layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(item), type)); + if (layout != NULL && layout->outputExists()) { + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + if (pt) { + p.push_back(Inkscape::SnapCandidatePoint((*pt) * sp_item_i2d_affine(item), Inkscape::SNAPSOURCE_TEXT_BASELINE, Inkscape::SNAPTARGET_TEXT_BASELINE)); + } } } diff --git a/src/sp-use.cpp b/src/sp-use.cpp index 76930086c..7962390c2 100644 --- a/src/sp-use.cpp +++ b/src/sp-use.cpp @@ -52,7 +52,7 @@ static void sp_use_update(SPObject *object, SPCtx *ctx, guint flags); static void sp_use_modified(SPObject *object, guint flags); static void sp_use_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags); -static void sp_use_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs); +static void sp_use_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); static void sp_use_print(SPItem *item, SPPrintContext *ctx); static gchar *sp_use_description(SPItem *item); static NRArenaItem *sp_use_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags); @@ -742,7 +742,7 @@ sp_use_get_original(SPUse *use) } static void -sp_use_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs) +sp_use_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) { g_assert (item != NULL); g_assert (SP_IS_ITEM(item)); @@ -755,7 +755,7 @@ sp_use_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(root); if (item_class.snappoints) { - item_class.snappoints(root, target, p, snapprefs); + item_class.snappoints(root, p, snapprefs); } } diff --git a/src/spray-context.cpp b/src/spray-context.cpp new file mode 100644 index 000000000..40bfc041e --- /dev/null +++ b/src/spray-context.cpp @@ -0,0 +1,1056 @@ +#define __SP_SPRAY_CONTEXT_C__ + +/* + * 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) + * + * Copyright (C) 2009 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glibmm/i18n.h> + +#include <numeric> + +#include "svg/svg.h" +#include "display/canvas-bpath.h" + +#include <glib/gmem.h> +#include "macros.h" +#include "document.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "unistd.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-spray.xpm" +#include <boost/optional.hpp> +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-scale-translate-ops.h" +#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-stop-fns.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/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "display/curve.h" +#include "livarot/Shape.h" +#include <2geom/isnan.h> +#include <2geom/transforms.h> +#include "preferences.h" +#include "style.h" +#include "box3d.h" +#include "sp-item-transform.h" +#include "filter-chemistry.h" +#include "sp-gaussian-blur-fns.h" +#include "sp-gaussian-blur.h" + +#include "spray-context.h" +#include "ui/dialog/dialog-manager.h" +#include "helper/action.h" + +#include <iostream> +using namespace std; + + +#define DDC_RED_RGBA 0xff0000ff + +#define DYNA_MIN_WIDTH 1.0e-6 + +static void sp_spray_context_class_init(SPSprayContextClass *klass); +static void sp_spray_context_init(SPSprayContext *ddc); +static void sp_spray_context_dispose(GObject *object); + +static void sp_spray_context_setup(SPEventContext *ec); +static void sp_spray_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val); +static gint sp_spray_context_root_handler(SPEventContext *ec, GdkEvent *event); + +static SPEventContextClass *parent_class = 0; + + +/** + * 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) ); +} + + +GtkType sp_spray_context_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPSprayContextClass), + NULL, NULL, + (GClassInitFunc) sp_spray_context_class_init, + NULL, NULL, + sizeof(SPSprayContext), + 4, + (GInstanceInitFunc) sp_spray_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSprayContext", &info, (GTypeFlags)0); + } + return type; +} + +static void sp_spray_context_class_init(SPSprayContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_spray_context_dispose; + + event_context_class->setup = sp_spray_context_setup; + event_context_class->set = sp_spray_context_set; + event_context_class->root_handler = sp_spray_context_root_handler; +} + +/* Method to rotate items */ +void sp_spray_rotate_rel(Geom::Point c,SPDesktop */*desktop*/,SPItem *item, Geom::Rotate const &rotation) +{ + Geom::Point center = c; + Geom::Translate const s(c); + Geom::Matrix affine = Geom::Matrix(s).inverse() * Geom::Matrix(rotation) * Geom::Matrix(s); + // Rotate item. + sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * (Geom::Matrix)affine); + // Use each item's own transform writer, consistent with sp_selection_apply_affine() + sp_item_write_transform(item, SP_OBJECT_REPR(item), 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 */ +void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale) +{ + Geom::Translate const s(c); + sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * s.inverse() * scale * s ); + sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform); +} + +static void sp_spray_context_init(SPSprayContext *tc) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(tc); + + event_context->cursor_shape = cursor_spray_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + + /* attributes */ + tc->dragging = FALSE; + tc->distrib = 1; + tc->width = 0.2; + tc->force = 0.2; + tc->ratio = 0; + tc->tilt=0; + tc->mean = 0.2; + tc->rotation_variation=0; + tc->standard_deviation=0.2; + tc->scale=1; + tc->scale_variation = 1; + tc->pressure = TC_DEFAULT_PRESSURE; + + tc->is_dilating = false; + tc->has_dilated = false; + + tc->do_h = true; + tc->do_s = true; + tc->do_l = true; + tc->do_o = false; + + new (&tc->style_set_connection) sigc::connection(); +} + +static void sp_spray_context_dispose(GObject *object) +{ + SPSprayContext *tc = SP_SPRAY_CONTEXT(object); + + tc->style_set_connection.disconnect(); + tc->style_set_connection.~connection(); + + if (tc->dilate_area) { + gtk_object_destroy(GTK_OBJECT(tc->dilate_area)); + tc->dilate_area = NULL; + } + + if (tc->_message_context) { + delete tc->_message_context; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +bool is_transform_modes(gint mode) +{ + return (mode == SPRAY_MODE_COPY || + mode == SPRAY_MODE_CLONE || + mode == SPRAY_MODE_SINGLE_PATH || + mode == SPRAY_OPTION); +} + +void sp_spray_update_cursor(SPSprayContext *tc, bool /*with_shift*/) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(tc); + SPDesktop *desktop = event_context->desktop; + + guint num = 0; + gchar *sel_message = NULL; + if (!desktop->selection->isEmpty()) { + num = g_slist_length((GSList *) desktop->selection->itemList()); + sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num); + } else { + sel_message = g_strdup_printf(_("<b>Nothing</b> selected")); + } + + + switch (tc->mode) { + case SPRAY_MODE_COPY: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray <b>copies</b> of the initial selection"), sel_message); + break; + case SPRAY_MODE_CLONE: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray <b>clones</b> of the initial selection"), sel_message); + break; + case SPRAY_MODE_SINGLE_PATH: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray in a <b>single path</b> of the initial selection"), sel_message); + break; + default: + break; + } + sp_event_context_update_cursor(event_context); + g_free(sel_message); +} + +static void sp_spray_context_setup(SPEventContext *ec) +{ + SPSprayContext *tc = SP_SPRAY_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) + ((SPEventContextClass *) parent_class)->setup(ec); + + { + /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ + SPCurve *c = new SPCurve(); + const double C1 = 0.552; + c->moveto(-1,0); + c->curveto(-1, C1, -C1, 1, 0, 1 ); + c->curveto(C1, 1, 1, C1, 1, 0 ); + c->curveto(1, -C1, C1, -1, 0, -1 ); + c->curveto(-C1, -1, -1, -C1, -1, 0 ); + c->closepath(); + tc->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(ec->desktop), c); + c->unref(); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(tc->dilate_area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(tc->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(tc->dilate_area); + } + + tc->is_drawing = false; + + tc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); + + sp_event_context_read(ec, "distrib"); + sp_event_context_read(ec, "width"); + sp_event_context_read(ec, "ratio"); + sp_event_context_read(ec, "tilt"); + sp_event_context_read(ec, "rotation_variation"); + sp_event_context_read(ec, "scale_variation"); + sp_event_context_read(ec, "mode"); + sp_event_context_read(ec, "population"); + sp_event_context_read(ec, "force"); + sp_event_context_read(ec, "mean"); + sp_event_context_read(ec, "standard_deviation"); + sp_event_context_read(ec, "usepressure"); + sp_event_context_read(ec, "Scale"); + sp_event_context_read(ec, "doh"); + sp_event_context_read(ec, "dol"); + sp_event_context_read(ec, "dos"); + sp_event_context_read(ec, "doo"); + + ; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/spray/selcue")) { + ec->enableSelectionCue(); + } + + if (prefs->getBool("/tools/spray/gradientdrag")) { + ec->enableGrDrag(); + } +} + +static void sp_spray_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val) +{ + SPSprayContext *tc = SP_SPRAY_CONTEXT(ec); + Glib::ustring path = val->getEntryName(); + + if (path == "width") { + tc->width = 0.01 * CLAMP(val->getInt(10), 1, 100); + } else if (path == "mode") { + tc->mode = val->getInt(); + sp_spray_update_cursor(tc, false); + } else if (path == "distribution") { + tc->distrib = val->getInt(1); + } else if (path == "population") { + tc->population = 0.01 * CLAMP(val->getInt(10), 1, 100); + } else if (path == "tilt") { + tc->tilt = CLAMP(val->getDouble(0.1), 0, 1000.0); + } else if (path == "ratio") { + tc->ratio = CLAMP(val->getDouble(), 0.0, 0.9); + } else if (path == "force") { + tc->force = CLAMP(val->getDouble(1.0), 0, 1.0); + } else if (path == "rotation_variation") { + tc->rotation_variation = CLAMP(val->getDouble(0.0), 0, 100.0); + } else if (path == "scale_variation") { + tc->scale_variation = CLAMP(val->getDouble(1.0), 0, 100.0); + } else if (path == "mean") { + tc->mean = 0.01 * CLAMP(val->getInt(10), 1, 100); + } else if (path == "standard_deviation") { + tc->standard_deviation = 0.01 * CLAMP(val->getInt(10), 1, 100); + } else if (path == "usepressure") { + tc->usepressure = val->getBool(); + } else if (path == "doh") { + tc->do_h = val->getBool(); + } else if (path == "dos") { + tc->do_s = val->getBool(); + } else if (path == "dol") { + tc->do_l = val->getBool(); + } else if (path == "doo") { + tc->do_o = val->getBool(); + } +} + +static void sp_spray_extinput(SPSprayContext *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; +} + +double get_dilate_radius(SPSprayContext *tc) +{ + + return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); + + +} + +double get_path_force(SPSprayContext *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; +} + +double get_path_mean(SPSprayContext *tc) +{ + return tc->mean; +} + +double get_path_standard_deviation(SPSprayContext *tc) +{ + return tc->standard_deviation; +} + +double get_move_force(SPSprayContext *tc) +{ + double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); + return force * tc->force; +} + +double get_move_mean(SPSprayContext *tc) +{ + return tc->mean; +} + +double get_move_standard_deviation(SPSprayContext *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 : + + */ +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); + +} + + + + + +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 = SP_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->getBounds(sp_item_i2doc_affine(item)); + if (a) { + SPItem *item_copied; + if(_fid<=population) + { + // duplicate + SPDocument *doc = SP_OBJECT_DOCUMENT(item); + Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc); + Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(item); + 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 = (SPItem *) 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; //initial Object + SPItem *item_copied; //Projected Object + SPItem *unionResult; //previous union + SPItem *son; //father copy + + int i=1; + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item1 = (SPItem *) items->data; + if (i==1) { + father=item1; + } + if (i==2) { + unionResult=item1; + } + i++; + } + SPDocument *doc = SP_OBJECT_DOCUMENT(father); + Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc); + Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(father); + Inkscape::XML::Node *parent = old_repr->parent(); + + Geom::OptRect a = father->getBounds(sp_item_i2doc_affine(father)); + if (a) { + if (i==2) { + Inkscape::XML::Node *copy1 = old_repr->duplicate(xml_doc); + parent->appendChild(copy1); + SPObject *new_obj1 = doc->getObjectByRepr(copy1); + son = (SPItem *) 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 = (SPItem *) 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(selection->desktop()); + selection->add(father); + Inkscape::GC::release(copy2); + did = true; + } + } + } else if (mode == SPRAY_MODE_CLONE) { + Geom::OptRect a = item->getBounds(sp_item_i2doc_affine(item)); + if (a) { + if(_fid<=population) { + SPItem *item_copied; + SPDocument *doc = SP_OBJECT_DOCUMENT(item); + Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc); + Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(item); + 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 + clone->setAttribute("xlink:href", g_strdup_printf("#%s", old_repr->attribute("id")), false); + + SPObject *clone_object = doc->getObjectByRepr(clone); + // conversion object->item + item_copied = (SPItem *) 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; +} + +bool sp_spray_dilate(SPSprayContext *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); + + + + bool do_fill = false, do_stroke = false, do_opacity = false; + guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/spray", true, &do_fill); + guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/spray", false, &do_stroke); + double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/spray", &do_opacity); + if (reverse) { + // 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))); + opacity_goal = 1 - opacity_goal; + } + + 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((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item = (SPItem *) 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; +} + +void sp_spray_update_area(SPSprayContext *tc) +{ + double radius = get_dilate_radius(tc); + Geom::Matrix 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); +} + +void sp_spray_switch_mode(SPSprayContext *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; + sp_spray_update_cursor (tc, with_shift); +} + +void sp_spray_switch_mode_temporarily(SPSprayContext *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/spray/mode", 0); + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("spray_tool_mode", mode); + // button has changed prefs, restore + prefs->setInt("/tools/spray/mode", now_mode); + // changing prefs changed tc->mode, restore back :) + tc->mode = mode; + sp_spray_update_cursor (tc, with_shift); +} + +gint sp_spray_context_root_handler(SPEventContext *event_context, + GdkEvent *event) +{ + SPSprayContext *tc = SP_SPRAY_CONTEXT(event_context); + SPDesktop *desktop = event_context->desktop; + + gint ret = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + sp_canvas_item_show(tc->dilate_area); + break; + case GDK_LEAVE_NOTIFY: + sp_canvas_item_hide(tc->dilate_area); + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !event_context->space_panning) { + + if (Inkscape::have_viable_layer(desktop, tc->_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)); + tc->last_push = desktop->dt2doc(motion_dt); + + sp_spray_extinput(tc, event); + + sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3); + tc->is_drawing = true; + tc->is_dilating = true; + tc->has_dilated = false; + + + + if(tc->is_dilating && event->button.button == 1 && !event_context->space_panning) + + sp_spray_dilate (tc, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT); + + + + tc->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(tc, event); + + // draw the dilating cursor + double radius = get_dilate_radius(tc); + Geom::Matrix 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(desktop->w2d(motion_w))); + sp_canvas_item_show(tc->dilate_area); + + guint num = 0; + if (!desktop->selection->isEmpty()) { + num = g_slist_length((GSList *) desktop->selection->itemList()); + } + if (num == 0) { + tc->_message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray.")); + } + + // dilating: + if (tc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { + sp_spray_dilate (tc, motion_w, motion_doc, motion_doc - tc->last_push, event->button.state & GDK_SHIFT_MASK? true : false); + //tc->last_push = motion_doc; + tc->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=tc->population; + tc->population=1.0; + desktop->setToolboxAdjustmentValue ("population", tc->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_UP: + { + if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false) + { + return TRUE; + } + tc->last_push = desktop->dt2doc(scroll_dt); + sp_spray_extinput(tc, event); + sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3); + tc->is_drawing = true; + tc->is_dilating = true; + tc->has_dilated = false; + if(tc->is_dilating && !event_context->space_panning) + + sp_spray_dilate (tc, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0),false); + + + + tc->has_dilated=true; + tc->population=temp; + + desktop->setToolboxAdjustmentValue ("population", tc->population * 100); + + ret = TRUE; + } + break; + case GDK_SCROLL_DOWN: + { + if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false) + { + return TRUE; + } + tc->last_push = desktop->dt2doc(scroll_dt); + sp_spray_extinput(tc, event); + sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3); + tc->is_drawing = true; + tc->is_dilating = true; + tc->has_dilated = false; + if(tc->is_dilating && !event_context->space_panning) + sp_spray_dilate (tc, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false); + + tc->has_dilated=true; + + 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)); + + sp_canvas_end_forced_full_redraws(desktop->canvas); + tc->is_drawing = false; + + if (tc->is_dilating && event->button.button == 1 && !event_context->space_panning) { + if (!tc->has_dilated) { + // if we did not rub, do a light tap + tc->pressure = 0.03; + sp_spray_dilate (tc, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT); + } + tc->is_dilating = false; + tc->has_dilated = false; + switch (tc->mode) { + case SPRAY_MODE_COPY: + sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray with copies")); + break; + case SPRAY_MODE_CLONE: + sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray with clones")); + break; + case SPRAY_MODE_SINGLE_PATH: + sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray in single path")); + break; + } + } + break; + } + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { +case GDK_j: if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_COPY, MOD__SHIFT); + ret = TRUE; + } +case GDK_J: if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_COPY, MOD__SHIFT); + ret = TRUE; + } + +break; + case GDK_m: + case GDK_M: + case GDK_0: + + break; + case GDK_i: + case GDK_I: + case GDK_k: if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT); + ret = TRUE; + } + case GDK_K:if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT); + ret = TRUE; + } +break; + + case GDK_l: if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_CLONE, MOD__SHIFT); + ret = TRUE; + } + + case GDK_L: + if (MOD__SHIFT_ONLY) { + sp_spray_switch_mode(tc, SPRAY_MODE_CLONE, MOD__SHIFT); + ret = TRUE; + } + break; + case GDK_Up: + case GDK_KP_Up: + if (!MOD__CTRL_ONLY) { + tc->scale += 0.05; + + //desktop->setToolboxAdjustmentValue ("spray-force", tc->force * 100); + ret = TRUE; + } + break; + case GDK_Down: + case GDK_KP_Down: + if (!MOD__CTRL_ONLY) { + + tc->scale -= 0.05; + if (tc->scale < 0.0) + tc->scale = 0.0; + //desktop->setToolboxAdjustmentValue ("spray-force", tc->force * 100); + + ret = TRUE; + + } + break; + case GDK_Right: + case GDK_KP_Right: + if (!MOD__CTRL_ONLY) { + tc->width += 0.01; + if (tc->width > 1.0) + tc->width = 1.0; + desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100); // the same spinbutton is for alt+x + sp_spray_update_area(tc); + ret = TRUE; + } + break; + case GDK_Left: + case GDK_KP_Left: + if (!MOD__CTRL_ONLY) { + tc->width -= 0.01; + if (tc->width < 0.01) + tc->width = 0.01; + desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100); + sp_spray_update_area(tc); + ret = TRUE; + } + break; + case GDK_Home: + case GDK_KP_Home: + tc->width = 0.01; + desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100); + sp_spray_update_area(tc); + ret = TRUE; + break; + case GDK_End: + case GDK_KP_End: + tc->width = 1.0; + desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100); + sp_spray_update_area(tc); + ret = TRUE; + break; + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-spray"); + ret = TRUE; + } + break; + + case GDK_Shift_L: + case GDK_Shift_R: + sp_spray_update_cursor(tc, true); + break; +/*Set the scale to 1*/ + case GDK_Control_L: + tc->scale=1; + default: + break; + } + break; + + case GDK_KEY_RELEASE: { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (get_group0_keyval(&event->key)) { + case GDK_Shift_L: + case GDK_Shift_R: + sp_spray_update_cursor(tc, false); + break; + case GDK_Control_L: + case GDK_Control_R: + sp_spray_switch_mode (tc, prefs->getInt("/tools/spray/mode"), MOD__SHIFT); + tc->_message_context->clear(); + break; + default: + sp_spray_switch_mode (tc, prefs->getInt("/tools/spray/mode"), MOD__SHIFT); + break; + } + } + + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : + diff --git a/src/spray-context.h b/src/spray-context.h new file mode 100644 index 000000000..ab2434223 --- /dev/null +++ b/src/spray-context.h @@ -0,0 +1,123 @@ +#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 "event-context.h" +#include <display/display-forward.h> +#include <libnr/nr-point.h> +//#include "ui/widget/spray-option.h" +#include "ui/dialog/dialog.h" + +#define SP_TYPE_SPRAY_CONTEXT (sp_spray_context_get_type()) +#define SP_SPRAY_CONTEXT(o) (GTK_CHECK_CAST((o), SP_TYPE_SPRAY_CONTEXT, SPSprayContext)) +#define SP_SPRAY_CONTEXT_CLASS(k) (GTK_CHECK_CLASS_CAST((k), SP_TYPE_SPRAY_CONTEXT, SPSprayContextClass)) +#define SP_IS_SPRAY_CONTEXT(o) (GTK_CHECK_TYPE((o), SP_TYPE_SPRAY_CONTEXT)) +#define SP_IS_SPRAY_CONTEXT_CLASS(k) (GTK_CHECK_CLASS_TYPE((k), SP_TYPE_SPRAY_CONTEXT)) + +class SPSprayContext; +class SPSprayContextClass; + +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 + +enum { + SPRAY_MODE_COPY, + SPRAY_MODE_CLONE, + SPRAY_MODE_SINGLE_PATH, + SPRAY_OPTION, +}; + +struct SPSprayContext +{ + SPEventContext 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; + + Inkscape::MessageContext *_message_context; + + 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; +}; + +struct SPSprayContextClass +{ + SPEventContextClass parent_class; +}; + +GtkType sp_spray_context_get_type(void); + + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : + diff --git a/src/style.cpp b/src/style.cpp index 0b946f348..111018c2a 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -28,6 +28,7 @@ #include "svg/svg.h" #include "svg/svg-color.h" #include "svg/svg-icc-color.h" +#include "svg/svg-device-color.h" #include "display/canvas-bpath.h" #include "attributes.h" @@ -3184,6 +3185,17 @@ sp_style_read_ipaint(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocume } paint->value.color.icc = tmp; } + if (strneq(str, "device-gray(", 12) || + strneq(str, "device-rgb(", 11) || + strneq(str, "device-cmyk(", 12) || + strneq(str, "device-nchannel(", 16)) { + SVGDeviceColor* tmp = new SVGDeviceColor(); + if ( ! sp_svg_read_device_color( str, &str, tmp ) ) { + delete tmp; + tmp = 0; + } + paint->value.color.device = tmp; + } } } } diff --git a/src/svg/stringstream.cpp b/src/svg/stringstream.cpp index 6b9e512a1..431d5d97b 100644 --- a/src/svg/stringstream.cpp +++ b/src/svg/stringstream.cpp @@ -66,6 +66,31 @@ operator<<(Inkscape::SVGOStringStream &os, Geom::Point const & p) return os; } +Inkscape::SVGIStringStream::SVGIStringStream():std::istringstream() +{ + this->imbue(std::locale::classic()); + this->setf(std::ios::showpoint); + + /* This one is (currently) needed though, as we currently use ostr.precision as a sort of + variable for storing the desired precision: see our two precision methods and our operator<< + methods for float and double. */ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->precision(prefs->getInt("/options/svgoutput/numericprecision", 8)); +} + +Inkscape::SVGIStringStream::SVGIStringStream(const std::string& str):std::istringstream(str) +{ + this->imbue(std::locale::classic()); + this->setf(std::ios::showpoint); + + /* This one is (currently) needed though, as we currently use ostr.precision as a sort of + variable for storing the desired precision: see our two precision methods and our operator<< + methods for float and double. */ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->precision(prefs->getInt("/options/svgoutput/numericprecision", 8)); +} + + /* Local Variables: mode:c++ diff --git a/src/svg/stringstream.h b/src/svg/stringstream.h index 5fbf1976c..60ed74ecb 100644 --- a/src/svg/stringstream.h +++ b/src/svg/stringstream.h @@ -74,6 +74,13 @@ public: } }; +class SVGIStringStream:public std::istringstream { + +public: + SVGIStringStream(); + SVGIStringStream(const std::string &str); +}; + } Inkscape::SVGOStringStream &operator<<(Inkscape::SVGOStringStream &os, float d); diff --git a/src/svg/svg-color.cpp b/src/svg/svg-color.cpp index a8e24c311..7d43b68ee 100644 --- a/src/svg/svg-color.cpp +++ b/src/svg/svg-color.cpp @@ -35,6 +35,16 @@ #include "preferences.h" #include "svg-color.h" #include "svg-icc-color.h" +#include "svg-device-color.h" + +#if ENABLE_LCMS +#include <lcms.h> +#include "color.h" +#include "color-profile.h" +#include "document.h" +#include "inkscape.h" +#include "profile-manager.h" +#endif // ENABLE_LCMS using std::sprintf; @@ -341,9 +351,9 @@ sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 dfl) * this check wrapper. */ gchar const *end = str; guint32 const ret = internal_sp_svg_read_color(str, &end, dfl); - assert(ret == dfl && end == str + assert(((ret == dfl) && (end == str)) || (((ret & 0xff) == 0) - && str < end)); + && (str < end))); if (str < end) { gchar *buf = (gchar *) g_malloc(end + 1 - str); memcpy(buf, str, end - str); @@ -454,6 +464,42 @@ sp_svg_create_color_hash() return colors; } +#if ENABLE_LCMS +//helper function borrowed from src/widgets/sp-color-icc-selector.cpp: +void getThings( DWORD space, gchar const**& namers, gchar const**& tippies, guint const*& scalies ); + +void icc_color_to_sRGB(SVGICCColor* icc, guchar* r, guchar* g, guchar* b){ + guchar color_out[4]; + guchar color_in[4]; + if (icc){ +g_message("profile name: %s", icc->colorProfile.c_str()); + Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->profileManager->find(icc->colorProfile.c_str()); + if ( prof ) { + cmsHTRANSFORM trans = prof->getTransfToSRGB8(); + if ( trans ) { + gchar const** names = 0; + gchar const** tips = 0; + guint const* scales = 0; + getThings( prof->getColorSpace(), names, tips, scales ); + + guint count = _cmsChannelsOf( prof->getColorSpace() ); + if (count>4) count=4; //do we need it? Should we allow an arbitrary number of color values? Or should we limit to a maximum? (max==4?) + for (guint i=0;i<count; i++){ + color_in[i] = (guchar) ((((gdouble)icc->colors[i])*256.0) * (gdouble)scales[i]); +g_message("input[%d]: %d",i, color_in[i]); + } + + cmsDoTransform( trans, color_in, color_out, 1 ); +g_message("transform to sRGB done"); + } + *r = color_out[0]; + *g = color_out[1]; + *b = color_out[2]; + } + } +} +#endif //ENABLE_LCMS + /* * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z' @@ -536,7 +582,7 @@ bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor while ( g_ascii_isspace(*str) ) { str++; } - good &= *str == ')'; + good &= (*str == ')'); } } @@ -554,6 +600,117 @@ bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor return good; } + +bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest ) +{ + return sp_svg_read_icc_color(str, NULL, dest); +} + +bool sp_svg_read_device_color( gchar const *str, gchar const **end_ptr, SVGDeviceColor* dest) +{ + bool good = true; + unsigned int max_colors; + + if ( end_ptr ) { + *end_ptr = str; + } + if ( dest ) { + dest->colors.clear(); + } + + if ( !str ) { + // invalid input + good = false; + } else { + while ( g_ascii_isspace(*str) ) { + str++; + } + + dest->type = DEVICE_COLOR_INVALID; + if (strneq( str, "device-gray(", 12 )){ + dest->type = DEVICE_GRAY; + max_colors=1; + str += 12; + } + + if (strneq( str, "device-rgb(", 11 )){ + dest->type = DEVICE_RGB; + max_colors=3; + str += 11; + } + + if (strneq( str, "device-cmyk(", 12 )){ + dest->type = DEVICE_CMYK; + max_colors=4; + str += 12; + } + + if (strneq( str, "device-nchannel(", 16 )){ + dest->type = DEVICE_NCHANNEL; + max_colors=0; + str += 16; + } + + if ( dest->type != DEVICE_COLOR_INVALID ) { + while ( g_ascii_isspace(*str) ) { + str++; + } + + while ( *str && *str != ')' ) { + if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') { + gchar* endPtr = 0; + gdouble dbl = g_ascii_strtod( str, &endPtr ); + if ( !errno ) { + if ( dest ) { + dest->colors.push_back( dbl ); + } + str = endPtr; + } else { + good = false; + break; + } + + while ( g_ascii_isspace(*str) || *str == ',' ) { + str++; + } + } else { + break; + } + } + } + + // We need to have ended on a closing parenthesis + if ( good ) { + while ( g_ascii_isspace(*str) ) { + str++; + } + good &= (*str == ')'); + } + } + + if ( dest->colors.size() == 0) good=false; + if ( dest->type != DEVICE_NCHANNEL && (dest->colors.size() != max_colors)) good=false; + + if ( good ) { + if ( end_ptr ) { + *end_ptr = str; + } + } else { + if ( dest ) { + dest->type = DEVICE_COLOR_INVALID; + dest->colors.clear(); + } + } + + return good; +} + + +bool sp_svg_read_device_color( gchar const *str, SVGDeviceColor* dest) +{ + return sp_svg_read_device_color(str, NULL, dest); +} + /* Local Variables: mode:c++ diff --git a/src/svg/svg-color.h b/src/svg/svg-color.h index 692c1dd00..f4e534652 100644 --- a/src/svg/svg-color.h +++ b/src/svg/svg-color.h @@ -4,12 +4,16 @@ #include <glib/gtypes.h> class SVGICCColor; +class SVGDeviceColor; guint32 sp_svg_read_color(gchar const *str, unsigned int dfl); guint32 sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def); void sp_svg_write_color(char *buf, unsigned int buflen, unsigned int rgba32); bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest ); - +bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest ); +bool sp_svg_read_device_color( gchar const *str, gchar const **end_ptr, SVGDeviceColor* dest ); +bool sp_svg_read_device_color( gchar const *str, SVGDeviceColor* dest ); +void icc_color_to_sRGB(SVGICCColor* dest, guchar* r, guchar* g, guchar* b); #endif /* !SVG_SVG_COLOR_H_SEEN */ diff --git a/src/svg/svg-device-color.h b/src/svg/svg-device-color.h new file mode 100644 index 000000000..305133ed3 --- /dev/null +++ b/src/svg/svg-device-color.h @@ -0,0 +1,26 @@ +#ifndef SVG_DEVICE_COLOR_H_SEEN +#define SVG_DEVICE_COLOR_H_SEEN + +#include <string> +#include <vector> + +typedef enum {DEVICE_COLOR_INVALID, DEVICE_GRAY, DEVICE_RGB, DEVICE_CMYK, DEVICE_NCHANNEL} SVGDeviceColorType; + +struct SVGDeviceColor { + SVGDeviceColorType type; + std::vector<double> colors; +}; + + +#endif /* !SVG_DEVICE_COLOR_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:encoding=utf-8:textwidth=99 : diff --git a/src/svg/svg-length.cpp b/src/svg/svg-length.cpp index 942f74d46..94f1cf312 100644 --- a/src/svg/svg-length.cpp +++ b/src/svg/svg-length.cpp @@ -64,6 +64,7 @@ unsigned int sp_svg_number_read_d(gchar const *str, double *val) return 1; } +// TODO must add a buffer length parameter for safety: static unsigned int sp_svg_number_write_ui(gchar *buf, unsigned int val) { unsigned int i = 0; diff --git a/src/text-context.cpp b/src/text-context.cpp index e6f4f083b..fc28dc8e4 100644 --- a/src/text-context.cpp +++ b/src/text-context.cpp @@ -425,11 +425,18 @@ sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEve // find out item under mouse, disregarding groups item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE); if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { - sp_canvas_item_show(tc->indicator); + + Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped); + if (layout->inputTruncated()) { + SP_CTRLRECT(tc->indicator)->setColor(0xff0000ff, false, 0); + } else { + SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0); + } Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped); if (ibbox) { SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox); } + sp_canvas_item_show(tc->indicator); event_context->cursor_shape = cursor_text_insert_xpm; event_context->hot_x = 7; @@ -1590,18 +1597,30 @@ sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see) Inkscape::Text::Layout const *layout = te_get_layout(tc->text); int const nChars = layout->iteratorToCharIndex(layout->end()); + char *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 = sp_item_bbox_desktop(frame); if (frame_bbox) { SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox); } } - SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters); <b>Enter</b> to start new paragraph."), nChars); + + SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph."), nChars, trunc); } else { - SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters); <b>Enter</b> to start new line."), nChars); + SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters%s); <b>Enter</b> to start new line."), nChars, trunc); } } else { diff --git a/src/text-editing.cpp b/src/text-editing.cpp index 2bdee4c10..e93ebdffa 100644 --- a/src/text-editing.cpp +++ b/src/text-editing.cpp @@ -1843,6 +1843,37 @@ void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &sta text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } +bool is_part_of_text_subtree (SPObject *obj) +{ + return (SP_IS_TSPAN(obj) + || SP_IS_TEXT(obj) + || SP_IS_FLOWTEXT(obj) + || SP_IS_FLOWTSPAN(obj) + || SP_IS_FLOWDIV(obj) + || SP_IS_FLOWPARA(obj) + || SP_IS_FLOWLINE(obj) + || SP_IS_FLOWREGIONBREAK(obj)); +} + +bool is_top_level_text_object (SPObject *obj) +{ + return (SP_IS_TEXT(obj) + || SP_IS_FLOWTEXT(obj)); +} + +bool has_visible_text (SPObject *obj) +{ + if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty()) + return true; // maybe we should also check that it's not all whitespace? + + for (SPObject const *child = obj->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) { + if (has_visible_text((SPObject *) child)) + return true; + } + + return false; +} + /* Local Variables: mode:c++ diff --git a/src/text-editing.h b/src/text-editing.h index 83ddae77f..7e845dbc9 100644 --- a/src/text-editing.h +++ b/src/text-editing.h @@ -59,4 +59,8 @@ void sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layou void sp_te_adjust_linespacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by); void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css); +bool is_part_of_text_subtree (SPObject *obj); +bool is_top_level_text_object (SPObject *obj); +bool has_visible_text (SPObject *obj); + #endif diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index 380267408..5f33453f0 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -28,6 +28,7 @@ #include "select-context.h" #include "ui/tool/node-tool.h" #include "tweak-context.h" +#include "spray-context.h" #include "sp-path.h" #include "rect-context.h" #include "sp-rect.h" @@ -62,6 +63,7 @@ static char const *const tool_names[] = { "/tools/select", "/tools/nodes", "/tools/tweak", + "/tools/spray", "/tools/shapes/rect", "/tools/shapes/3dbox", "/tools/shapes/arc", @@ -134,6 +136,12 @@ tools_switch(SPDesktop *dt, int num) inkscape_eventcontext_set(sp_desktop_event_context(dt)); dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To tweak a path by pushing, select it and drag over it.")); break; + case TOOLS_SPRAY: + dt->set_event_context(SP_TYPE_SPRAY_CONTEXT, tool_names[num]); + dt->activate_guides(true); + inkscape_eventcontext_set(sp_desktop_event_context(dt)); + dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To spray a path by pushing, select it and drag over it.")); + break; case TOOLS_SHAPES_RECT: dt->set_event_context(SP_TYPE_RECT_CONTEXT, tool_names[num]); dt->activate_guides(false); diff --git a/src/tools-switch.h b/src/tools-switch.h index 36dd8f80b..4cc9aa93d 100644 --- a/src/tools-switch.h +++ b/src/tools-switch.h @@ -19,6 +19,7 @@ enum { TOOLS_SELECT, TOOLS_NODES, TOOLS_TWEAK, + TOOLS_SPRAY, TOOLS_SHAPES_RECT, TOOLS_SHAPES_3DBOX, TOOLS_SHAPES_ARC, diff --git a/src/transf_mat_3x4.cpp b/src/transf_mat_3x4.cpp index b7cd278d4..6b49dc44a 100644 --- a/src/transf_mat_3x4.cpp +++ b/src/transf_mat_3x4.cpp @@ -115,6 +115,7 @@ TransfMat3x4::pt_to_str (Proj::Axis axis) { return g_strdup(os.str().c_str()); } +/* Check for equality (with a small tolerance epsilon) */ bool TransfMat3x4::operator==(const TransfMat3x4 &rhs) const { @@ -129,29 +130,16 @@ TransfMat3x4::operator==(const TransfMat3x4 &rhs) const return true; } -/* multiply a projective matrix by an affine matrix */ +/* Multiply a projective matrix by an affine matrix (by only multiplying the 'affine part' of the + * projective matrix) */ TransfMat3x4 TransfMat3x4::operator*(Geom::Matrix const &A) const { TransfMat3x4 ret; - // Is it safe to always use the currently active document? - double h = sp_document_height(inkscape_active_document()); - - /* - * Note: The strange multiplication involving the document height is due to the buggy - * intertwining of SVG and document coordinates. Essentially, what we do is first - * convert from "real-world" to SVG coordinates, then apply the transformation A - * (by multiplying with the Geom::Matrix) and then convert back from SVG to real-world - * coordinates. Maybe there is even a more Inkscape-ish way to achieve this? - * Once Inkscape has gotton rid of the two different coordiate systems, we can change - * this function to an ordinary matrix multiplication. - */ for (int j = 0; j < 4; ++j) { - ret.tmat[0][j] = A[0]*tmat[0][j] + A[2]*(h*tmat[2][j] - tmat[1][j]) + A[4]*tmat[2][j]; - ret.tmat[1][j] = A[1]*tmat[0][j] + A[3]*(h*tmat[2][j] - tmat[1][j]) + A[5]*tmat[2][j]; + ret.tmat[0][j] = A[0]*tmat[0][j] + A[2]*tmat[1][j] + A[4]*tmat[2][j]; + ret.tmat[1][j] = A[1]*tmat[0][j] + A[3]*tmat[1][j] + A[5]*tmat[2][j]; ret.tmat[2][j] = tmat[2][j]; - - ret.tmat[1][j] = h*ret.tmat[2][j] - ret.tmat[1][j]; // switch back from SVG to desktop coordinates } return ret; diff --git a/src/ui/Makefile_insert b/src/ui/Makefile_insert index 3eb6c6b13..eb8966d11 100644 --- a/src/ui/Makefile_insert +++ b/src/ui/Makefile_insert @@ -9,4 +9,6 @@ ink_common_sources += \ ui/previewable.h \ ui/previewfillable.h \ ui/previewholder.cpp \ - ui/previewholder.h + ui/previewholder.h \ + ui/uxmanager.cpp \ + ui/uxmanager.h diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert index 565a24ecc..76cdd3517 100644 --- a/src/ui/dialog/Makefile_insert +++ b/src/ui/dialog/Makefile_insert @@ -71,6 +71,8 @@ ink_common_sources += \ ui/dialog/panel-dialog.h \ ui/dialog/print.cpp \ ui/dialog/print.h \ + ui/dialog/print-colors-preview-dialog.cpp \ + ui/dialog/print-colors-preview-dialog.h \ ui/dialog/scriptdialog.cpp \ ui/dialog/scriptdialog.h \ ui/dialog/svg-fonts-dialog.cpp \ diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index f38d674fd..8c8d64ec0 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -706,14 +706,16 @@ private : { if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) { Inkscape::Text::Layout const *layout = te_get_layout(*it); - Geom::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it); - if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X]; - if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y]; - if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X]; - if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y]; - - Baselines b (*it, base, _orientation); - sorted.push_back(b); + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + if (pt) { + Geom::Point base = *pt * sp_item_i2d_affine(*it); + if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X]; + if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y]; + if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X]; + if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y]; + Baselines b (*it, base, _orientation); + sorted.push_back(b); + } } } @@ -747,11 +749,14 @@ private : { if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) { Inkscape::Text::Layout const *layout = te_get_layout(*it); - Geom::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it); - Geom::Point t(0.0, 0.0); - t[_orientation] = b_min[_orientation] - base[_orientation]; - sp_item_move_rel(*it, Geom::Translate(t)); - changed = true; + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + if (pt) { + Geom::Point base = *pt * sp_item_i2d_affine(*it); + Geom::Point t(0.0, 0.0); + t[_orientation] = b_min[_orientation] - base[_orientation]; + sp_item_move_rel(*it, Geom::Translate(t)); + changed = true; + } } } @@ -808,7 +813,7 @@ AlignAndDistribute::AlignAndDistribute() _("Align left edges"), 0, 1); addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_CENTER, - _("Center objects horizontally"), + _("Center on vertical axis"), 0, 2); addAlignButton(INKSCAPE_ICON_ALIGN_HORIZONTAL_RIGHT, _("Align right sides"), diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index d1b818d23..30cbed649 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -40,6 +40,7 @@ #include "ui/dialog/icon-preview.h" #include "ui/dialog/floating-behavior.h" #include "ui/dialog/dock-behavior.h" +#include "ui/dialog/print-colors-preview-dialog.h" #include "preferences.h" #ifdef ENABLE_SVG_FONTS @@ -88,7 +89,6 @@ DialogManager::DialogManager() { int dialogs_type = prefs->getIntLimited("/options/dialogtype/value", DOCK, 0, 1); if (dialogs_type == FLOATING) { - registerFactory("AlignAndDistribute", &create<AlignAndDistribute, FloatingBehavior>); registerFactory("DocumentMetadata", &create<DocumentMetadata, FloatingBehavior>); registerFactory("DocumentProperties", &create<DocumentProperties, FloatingBehavior>); @@ -102,6 +102,7 @@ DialogManager::DialogManager() { registerFactory("LivePathEffect", &create<LivePathEffectEditor, FloatingBehavior>); registerFactory("Memory", &create<Memory, FloatingBehavior>); registerFactory("Messages", &create<Messages, FloatingBehavior>); + registerFactory("PrintColorsPreviewDialog", &create<PrintColorsPreviewDialog, FloatingBehavior>); registerFactory("Script", &create<ScriptDialog, FloatingBehavior>); #ifdef ENABLE_SVG_FONTS registerFactory("SvgFontsDialog", &create<SvgFontsDialog, FloatingBehavior>); @@ -111,7 +112,7 @@ DialogManager::DialogManager() { registerFactory("Trace", &create<TraceDialog, FloatingBehavior>); registerFactory("Transformation", &create<Transformation, FloatingBehavior>); registerFactory("UndoHistory", &create<UndoHistory, FloatingBehavior>); - registerFactory("InputDevices", &create<InputDialog, FloatingBehavior>); + registerFactory("InputDevices", &create<InputDialog, FloatingBehavior>); } else { @@ -128,6 +129,7 @@ DialogManager::DialogManager() { registerFactory("LivePathEffect", &create<LivePathEffectEditor, DockBehavior>); registerFactory("Memory", &create<Memory, DockBehavior>); registerFactory("Messages", &create<Messages, DockBehavior>); + registerFactory("PrintColorsPreviewDialog", &create<PrintColorsPreviewDialog, DockBehavior>); registerFactory("Script", &create<ScriptDialog, DockBehavior>); #ifdef ENABLE_SVG_FONTS registerFactory("SvgFontsDialog", &create<SvgFontsDialog, DockBehavior>); diff --git a/src/ui/dialog/document-properties.cpp b/src/ui/dialog/document-properties.cpp index 7e31b874a..86baa85cd 100644 --- a/src/ui/dialog/document-properties.cpp +++ b/src/ui/dialog/document-properties.cpp @@ -222,7 +222,7 @@ DocumentProperties::build_page() Gtk::Label* label_bor = manage (new Gtk::Label); label_bor->set_markup (_("<b>Border</b>")); Gtk::Label *label_for = manage (new Gtk::Label); - label_for->set_markup (_("<b>Format</b>")); + label_for->set_markup (_("<b>Page Size</b>")); _page_sizer.init(); Gtk::Widget *const widget_array[] = @@ -353,6 +353,7 @@ DocumentProperties::populate_available_profiles(){ while ((filename = (gchar *)g_dir_read_name(directory)) != NULL) { gchar* full = g_build_filename(it->c_str(), filename, NULL); if ( !Inkscape::IO::file_test( full, G_FILE_TEST_IS_DIR ) ) { + cmsErrorAction( LCMS_ERROR_SHOW ); cmsHPROFILE hProfile = cmsOpenProfileFromFile(full, "r"); if (hProfile != NULL){ const gchar* name; @@ -414,7 +415,8 @@ static void sanitizeName( Glib::ustring& str ) } } -void DocumentProperties::linkSelectedProfile() +void +DocumentProperties::linkSelectedProfile() { //store this profile in the SVG document (create <color-profile> element in the XML) // TODO remove use of 'active' desktop diff --git a/src/ui/dialog/filter-effects-dialog.cpp b/src/ui/dialog/filter-effects-dialog.cpp index c7f505046..1345ffe55 100644 --- a/src/ui/dialog/filter-effects-dialog.cpp +++ b/src/ui/dialog/filter-effects-dialog.cpp @@ -4,7 +4,7 @@ /* Authors: * Nicholas Bishop <nicholasbishop@gmail.org> * Rodrigo Kumpera <kumpera@gmail.com> - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2007 Authors * diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index b81c98b0f..11850cffc 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -458,6 +458,12 @@ void InkscapePreferences::initPageTools() AddSelcueCheckbox(_page_tweak, "/tools/tweak", true); AddGradientCheckbox(_page_tweak, "/tools/tweak", false); + //Spray + this->AddPage(_page_spray, _("Spray"), iter_tools, PREFS_PAGE_TOOLS_SPRAY); + this->AddNewObjectsStyle(_page_spray, "/tools/spray", _("Paint objects with:")); + AddSelcueCheckbox(_page_spray, "/tools/spray", true); + AddGradientCheckbox(_page_spray, "/tools/spray", false); + //Zoom this->AddPage(_page_zoom, _("Zoom"), iter_tools, PREFS_PAGE_TOOLS_ZOOM); AddSelcueCheckbox(_page_zoom, "/tools/zoom", true); @@ -479,15 +485,15 @@ void InkscapePreferences::initPageTools() this->AddNewObjectsStyle(_page_3dbox, "/tools/shapes/3dbox"); this->AddConvertGuidesCheckbox(_page_3dbox, "/tools/shapes/3dbox", true); - //ellipse + //Ellipse this->AddPage(_page_ellipse, _("Ellipse"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_ELLIPSE); this->AddNewObjectsStyle(_page_ellipse, "/tools/shapes/arc"); - //star + //Star this->AddPage(_page_star, _("Star"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_STAR); this->AddNewObjectsStyle(_page_star, "/tools/shapes/star"); - //spiral + //Spiral this->AddPage(_page_spiral, _("Spiral"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_SPIRAL); this->AddNewObjectsStyle(_page_spiral, "/tools/shapes/spiral"); @@ -531,6 +537,11 @@ void InkscapePreferences::initPageTools() this->AddPage(_page_text, _("Text"), iter_tools, PREFS_PAGE_TOOLS_TEXT); this->AddSelcueCheckbox(_page_text, "/tools/text", true); this->AddGradientCheckbox(_page_text, "/tools/text", true); + { + PrefCheckButton* cb = Gtk::manage( new PrefCheckButton); + cb->init ( _("Show font samples in the drop-down list"), "/tools/text/show_sample_in_list", 1); + _page_text.add_line( false, "", *cb, "", _("Show font samples alongside font names in the drop-down list in Text bar")); + } this->AddNewObjectsStyle(_page_text, "/tools/text"); //Gradient @@ -739,6 +750,11 @@ void InkscapePreferences::initPageFilters() _page_filters.add_line(true, "", _show_filters_info_box, "", _("Show icons and descriptions for the filter primitives available at the filter effects dialog.")); + /* threaded blur */ //related comments/widgets/functions should be renamed and option should be moved elsewhere when inkscape is fully multi-threaded + _filter_multi_threaded.init("/options/threading/numthreads", 1.0, 8.0, 1.0, 2.0, 4.0, true, false); + _page_filters.add_line( false, _("Number of Threads:"), _filter_multi_threaded, _("(requires restart)"), + _("Configure number of processors/threads to use with rendering of gaussian blur."), false); + this->AddPage(_page_filters, _("Filters"), PREFS_PAGE_FILTERS); } diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 8ac63dd7c..638c84598 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -43,6 +43,7 @@ enum { PREFS_PAGE_TOOLS_SELECTOR, PREFS_PAGE_TOOLS_NODE, PREFS_PAGE_TOOLS_TWEAK, + PREFS_PAGE_TOOLS_SPRAY, PREFS_PAGE_TOOLS_ZOOM, PREFS_PAGE_TOOLS_SHAPES, PREFS_PAGE_TOOLS_SHAPES_RECT, @@ -118,7 +119,7 @@ protected: _page_clones, _page_mask, _page_transforms, _page_filters, _page_select, _page_importexport, _page_cms, _page_grids, _page_svgoutput, _page_misc, _page_ui, _page_save, _page_bitmaps, _page_spellcheck; - DialogPage _page_selector, _page_node, _page_tweak, _page_zoom, _page_shapes, _page_pencil, _page_pen, + DialogPage _page_selector, _page_node, _page_tweak, _page_spray, _page_zoom, _page_shapes, _page_pencil, _page_pen, _page_calligraphy, _page_text, _page_gradient, _page_connector, _page_dropper, _page_lpetool; DialogPage _page_rectangle, _page_3dbox, _page_ellipse, _page_star, _page_spiral, _page_paintbucket, _page_eraser; @@ -174,6 +175,7 @@ protected: PrefRadioButton _blur_quality_best, _blur_quality_better, _blur_quality_normal, _blur_quality_worse, _blur_quality_worst; PrefRadioButton _filter_quality_best, _filter_quality_better, _filter_quality_normal, _filter_quality_worse, _filter_quality_worst; PrefCheckButton _show_filters_info_box; + PrefSpinButton _filter_multi_threaded; PrefCheckButton _trans_scale_stroke, _trans_scale_corner, _trans_gradient,_trans_pattern; PrefRadioButton _trans_optimized, _trans_preserved; diff --git a/src/ui/dialog/print-colors-preview-dialog.cpp b/src/ui/dialog/print-colors-preview-dialog.cpp new file mode 100644 index 000000000..f4d83c271 --- /dev/null +++ b/src/ui/dialog/print-colors-preview-dialog.cpp @@ -0,0 +1,100 @@ +/** @file + * @brief Print Colors Preview dialog - implementation + */ +/* Authors: + * Felipe C. da S. Sanches <juca@members.fsf.org> + * + * Copyright (C) 2009 Authors + * Released under GNU GPLv2 (or later). Read the file 'COPYING' for more information. + */ + +#include "desktop.h" +#include "print-colors-preview-dialog.h" +#include "preferences.h" +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace UI { +namespace Dialog { + +//Yes, I know we shouldn't hardcode CMYK. This class needs to be refactored +// in order to accomodate spot colors and color components defined using +// ICC colors. --Juca + +void PrintColorsPreviewDialog::toggle_cyan(){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/printcolorspreview/cyan", cyan->get_active()); + + SPDesktop *desktop = getDesktop(); + desktop->setDisplayModePrintColorsPreview(); +} + +void PrintColorsPreviewDialog::toggle_magenta(){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/printcolorspreview/magenta", magenta->get_active()); + + SPDesktop *desktop = getDesktop(); + desktop->setDisplayModePrintColorsPreview(); +} + +void PrintColorsPreviewDialog::toggle_yellow(){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/printcolorspreview/yellow", yellow->get_active()); + + SPDesktop *desktop = getDesktop(); + desktop->setDisplayModePrintColorsPreview(); +} + +void PrintColorsPreviewDialog::toggle_black(){ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/printcolorspreview/black", black->get_active()); + + SPDesktop *desktop = getDesktop(); + desktop->setDisplayModePrintColorsPreview(); +} + +PrintColorsPreviewDialog::PrintColorsPreviewDialog() + : UI::Widget::Panel("", "/dialogs/printcolorspreview", SP_VERB_DIALOG_PRINT_COLORS_PREVIEW) +{ + Gtk::VBox* vbox = Gtk::manage(new Gtk::VBox()); + + cyan = new Gtk::ToggleButton(_("Cyan")); + vbox->pack_start( *cyan, false, false ); +// tips.set_tip((*cyan), _("Render cyan separation")); + cyan->signal_clicked().connect( sigc::mem_fun(*this, &PrintColorsPreviewDialog::toggle_cyan) ); + + magenta = new Gtk::ToggleButton(_("Magenta")); + vbox->pack_start( *magenta, false, false ); +// tips.set_tip((*magenta), _("Render magenta separation")); + magenta->signal_clicked().connect( sigc::mem_fun(*this, &PrintColorsPreviewDialog::toggle_magenta) ); + + yellow = new Gtk::ToggleButton(_("Yellow")); + vbox->pack_start( *yellow, false, false ); +// tips.set_tip((*yellow), _("Render yellow separation")); + yellow->signal_clicked().connect( sigc::mem_fun(*this, &PrintColorsPreviewDialog::toggle_yellow) ); + + black = new Gtk::ToggleButton(_("Black")); + vbox->pack_start( *black, false, false ); +// tips.set_tip((*black), _("Render black separation")); + black->signal_clicked().connect( sigc::mem_fun(*this, &PrintColorsPreviewDialog::toggle_black) ); + + gint val; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + val = prefs->getBool("/options/printcolorspreview/cyan"); + cyan->set_active( val != 0 ); + val = prefs->getBool("/options/printcolorspreview/magenta"); + magenta->set_active( val != 0 ); + val = prefs->getBool("/options/printcolorspreview/yellow"); + yellow->set_active( val != 0 ); + val = prefs->getBool("/options/printcolorspreview/black"); + black->set_active( val != 0 ); + + _getContents()->add(*vbox); + _getContents()->show_all(); +} + +PrintColorsPreviewDialog::~PrintColorsPreviewDialog(){} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape diff --git a/src/ui/dialog/print-colors-preview-dialog.h b/src/ui/dialog/print-colors-preview-dialog.h new file mode 100644 index 000000000..246908556 --- /dev/null +++ b/src/ui/dialog/print-colors-preview-dialog.h @@ -0,0 +1,48 @@ +/** @file + * @brief Print Colors Preview dialog + */ +/* Authors: + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> + * + * Copyright (C) 2009 Authors + * Released under GNU GPLv2 (or later). Read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_DIALOG_PRINT_COLORS_PREVIEW_H +#define INKSCAPE_UI_DIALOG_PRINT_COLORS_PREVIEW_H + +#include "ui/widget/panel.h" +#include "verbs.h" + +#include <gtkmm.h> +#include <gtkmm/box.h> + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class PrintColorsPreviewDialog : public UI::Widget::Panel { +public: + PrintColorsPreviewDialog(); + ~PrintColorsPreviewDialog(); + + static PrintColorsPreviewDialog &getInstance() + { return *new PrintColorsPreviewDialog(); } + +private: + void toggle_cyan(); + void toggle_magenta(); + void toggle_yellow(); + void toggle_black(); + + Gtk::ToggleButton* cyan; + Gtk::ToggleButton* magenta; + Gtk::ToggleButton* yellow; + Gtk::ToggleButton* black; +}; + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +#endif //#ifndef INKSCAPE_UI_PRINT_COLORS_PREVIEW_H diff --git a/src/ui/dialog/print.cpp b/src/ui/dialog/print.cpp index f9db265d6..60cab06a2 100644 --- a/src/ui/dialog/print.cpp +++ b/src/ui/dialog/print.cpp @@ -31,11 +31,15 @@ -static void -draw_page (GtkPrintOperation *operation, - GtkPrintContext *context, - gint /*page_nr*/, - gpointer user_data) +static void draw_page( +#ifdef WIN32 + GtkPrintOperation *operation, +#else + GtkPrintOperation *, +#endif + GtkPrintContext *context, + gint /*page_nr*/, + gpointer user_data) { struct workaround_gtkmm *junk = (struct workaround_gtkmm*)user_data; //printf("%s %d\n",__FUNCTION__, page_nr); diff --git a/src/ui/dialog/svg-fonts-dialog.cpp b/src/ui/dialog/svg-fonts-dialog.cpp index 5f86196b1..cb22e029b 100644 --- a/src/ui/dialog/svg-fonts-dialog.cpp +++ b/src/ui/dialog/svg-fonts-dialog.cpp @@ -2,7 +2,7 @@ * @brief SVG Fonts dialog - implementation */ /* Authors: - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Authors * Released under GNU GPLv2 (or later). Read the file 'COPYING' for more information. diff --git a/src/ui/dialog/svg-fonts-dialog.h b/src/ui/dialog/svg-fonts-dialog.h index e6042ed42..e819187a1 100644 --- a/src/ui/dialog/svg-fonts-dialog.h +++ b/src/ui/dialog/svg-fonts-dialog.h @@ -2,7 +2,7 @@ * @brief SVG Fonts dialog */ /* Authors: - * Felipe Corrêa da Silva Sanches <felipe.sanches@gmail.com> + * Felipe Corrêa da Silva Sanches <juca@members.fsf.org> * * Copyright (C) 2008 Authors * Released under GNU GPLv2 (or later). Read the file 'COPYING' for more information. diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp index 1f708e3de..450d4202d 100644 --- a/src/ui/dialog/swatches.cpp +++ b/src/ui/dialog/swatches.cpp @@ -47,7 +47,7 @@ #include "display/nr-plain-stuff.h" #include "sp-gradient-reference.h" -//#define USE_DOCUMENT_PALETTE 1 +#define USE_DOCUMENT_PALETTE 1 namespace Inkscape { namespace UI { diff --git a/src/ui/icon-names.h b/src/ui/icon-names.h index f9a6f2a7d..76e76ea34 100644 --- a/src/ui/icon-names.h +++ b/src/ui/icon-names.h @@ -56,10 +56,18 @@ "color-picker" #define INKSCAPE_ICON_COLOR_REMOVE \ "color-remove" +#define INKSCAPE_ICON_CONNECTOR_EDIT \ + "connector-edit" #define INKSCAPE_ICON_CONNECTOR_AVOID \ "connector-avoid" #define INKSCAPE_ICON_CONNECTOR_IGNORE \ "connector-ignore" +#define INKSCAPE_ICON_CONNECTOR_ORTHOGONAL \ + "connector-orthogonal" +#define INKSCAPE_ICON_CONNECTOR_NEW_CONNPOINT \ + "connector-new-connpoint" +#define INKSCAPE_ICON_CONNECTOR_REMOVE_CONNPOINT \ + "connector-remove-connpoint" #define INKSCAPE_ICON_DIALOG_ALIGN_AND_DISTRIBUTE \ "dialog-align-and-distribute" #define INKSCAPE_ICON_DIALOG_FILL_AND_STROKE \ @@ -456,6 +464,14 @@ "snap-nodes-smooth" #define INKSCAPE_ICON_SNAP_PAGE \ "snap-page" +#define INKSCAPE_ICON_SPRAY_COPY_MODE \ + "spray-copy-mode" +#define INKSCAPE_ICON_SPRAY_CLONE_MODE \ + "spray-clone-mode" +#define INKSCAPE_ICON_SPRAY_UNION_MODE \ + "spray-union-mode" +#define INKSCAPE_ICON_DIALOG_SPRAY_OPTIONS \ + "dialog-spray-options" #define INKSCAPE_ICON_STROKE_CAP_BUTT \ "stroke-cap-butt" #define INKSCAPE_ICON_STROKE_CAP_ROUND \ @@ -488,6 +504,8 @@ "tool-pointer" #define INKSCAPE_ICON_TOOL_TWEAK \ "tool-tweak" +#define INKSCAPE_ICON_TOOL_SPRAY \ + "tool-spray" #define INKSCAPE_ICON_TRANSFORM_AFFECT_GRADIENT \ "transform-affect-gradient" #define INKSCAPE_ICON_TRANSFORM_AFFECT_PATTERN \ diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index 33d96c706..2cc9bc97b 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -280,8 +280,12 @@ void MultiPathManipulator::joinNodes() } _selection.insert(i->first.ptr()); } - // Second part replaces contiguous selections of nodes with single nodes - invokeForAll(&PathManipulator::weldNodes, preserve_pos); + + if (joins.empty()) { + // Second part replaces contiguous selections of nodes with single nodes + invokeForAll(&PathManipulator::weldNodes, preserve_pos); + } + _doneWithCleanup(_("Join nodes")); } diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index 889f4a793..303c0fb75 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -849,14 +849,14 @@ void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) SnapManager &sm = _desktop->namedview->snap_manager; Inkscape::SnapPreferences::PointType t = Inkscape::SnapPreferences::SNAPPOINT_NODE; bool snap = sm.someSnapperMightSnap(); - std::vector< std::pair<Geom::Point, int> > unselected; + std::vector<Inkscape::SnapCandidatePoint> unselected; if (snap) { /* setup * TODO We are doing this every time a snap happens. It should once be done only once * per drag - maybe in the grabbed handler? * TODO Unselected nodes vector must be valid during the snap run, because it is not * copied. Fix this in snap.h and snap.cpp, then the above. - * TODO Snapping to unselected segments of selected paths doesn't work. */ + * TODO Snapping to unselected segments of selected paths doesn't work yet. */ // Build the list of unselected nodes. typedef ControlPointSelection::Set Set; @@ -864,7 +864,8 @@ void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { if (!(*i)->selected()) { Node *n = static_cast<Node*>(*i); - unselected.push_back(std::make_pair((*i)->position(), (int) n->_snapTargetType())); + Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType()); + unselected.push_back(p); } } sm.setupIgnoreSelection(_desktop, true, &unselected); @@ -881,8 +882,8 @@ void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) // TODO: combine these two branches by modifying snap.h / snap.cpp if (snap) { Inkscape::SnappedPoint fp, bp; - fp = sm.constrainedSnap(t, position(), _snapSourceType(), line_front); - bp = sm.constrainedSnap(t, position(), _snapSourceType(), line_back); + fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_front); + bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_back); if (fp.isOtherSnapBetter(bp, false)) { bp.getPoint(new_pos); @@ -905,8 +906,8 @@ void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) Inkscape::SnappedPoint fp, bp; Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0)); Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1)); - fp = sm.constrainedSnap(t, position(), _snapSourceType(), line_x); - bp = sm.constrainedSnap(t, position(), _snapSourceType(), line_y); + fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_x); + bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_y); if (fp.isOtherSnapBetter(bp, false)) { fp = bp; diff --git a/src/ui/uxmanager.cpp b/src/ui/uxmanager.cpp new file mode 100644 index 000000000..ddc28a858 --- /dev/null +++ b/src/ui/uxmanager.cpp @@ -0,0 +1,156 @@ +/** \file + * Desktop widget implementation + */ +/* Authors: + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2010 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <algorithm> + +#include "uxmanager.h" +#include "util/ege-tags.h" +#include "widgets/toolbox.h" +#include "widgets/desktop-widget.h" + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif // GDK_WINDOWING_X11 + +using std::map; +using std::vector; + + +gchar const* KDE_WINDOW_MANAGER_NAME = "KWin"; +gchar const* UNKOWN_WINDOW_MANAGER_NAME = "unknown"; + + +static vector<SPDesktop*> desktops; +static vector<SPDesktopWidget*> dtws; +static map<SPDesktop*, vector<GtkWidget*> > trackedBoxes; + + + +namespace Inkscape { +namespace UI { + +UXManager* instance = 0; + +UXManager* UXManager::getInstance() +{ + if (!instance) { + instance = new UXManager(); + } + return instance; +} + + +UXManager::UXManager() : + floatwindowIssues(false) +{ + ege::TagSet tags; + tags.setLang("en"); + + tags.addTag(ege::Tag("General")); + tags.addTag(ege::Tag("Icons")); + +#ifdef GDK_WINDOWING_X11 + char const* wmName = gdk_x11_screen_get_window_manager_name( gdk_screen_get_default() ); + //g_message("Window manager is [%s]", wmName); + + //if (g_ascii_strcasecmp( wmName, UNKOWN_WINDOW_MANAGER_NAME ) == 0) { + if (g_ascii_strcasecmp( wmName, KDE_WINDOW_MANAGER_NAME ) == 0) { + floatwindowIssues = true; + } +#elif GDK_WINDOWING_WIN32 + floatwindowIssues = true; +#endif // GDK_WINDOWING_WIN32 +} + +UXManager::~UXManager() +{ +} + + +bool UXManager::isFloatWindowProblem() const +{ + return floatwindowIssues; +} + +void UXManager::setTask(SPDesktop* dt, gint val) +{ + for (vector<SPDesktopWidget*>::iterator it = dtws.begin(); it != dtws.end(); ++it) { + SPDesktopWidget* dtw = *it; + if (dtw->desktop == dt) { + if (val == 0) { + dtw->setToolboxPosition("ToolToolbar", GTK_POS_LEFT); + dtw->setToolboxPosition("CommandsToolbar", GTK_POS_TOP); + dtw->setToolboxPosition("SnapToolbar", GTK_POS_TOP); + // for now skip "AuxToolbar"; + } else { + dtw->setToolboxPosition("ToolToolbar", GTK_POS_TOP); + dtw->setToolboxPosition("CommandsToolbar", GTK_POS_LEFT); + dtw->setToolboxPosition("SnapToolbar", GTK_POS_RIGHT); + // for now skip "AuxToolbar"; + } + break; + } + } +} + + +void UXManager::addTrack( SPDesktopWidget* dtw ) +{ + if (std::find(dtws.begin(), dtws.end(), dtw) == dtws.end()) { + dtws.push_back(dtw); + } +} + +void UXManager::delTrack( SPDesktopWidget* dtw ) +{ + vector<SPDesktopWidget*>::iterator iter = std::find(dtws.begin(), dtws.end(), dtw); + if (iter != dtws.end()) { + dtws.erase(iter); + } +} + +void UXManager::connectToDesktop( vector<GtkWidget *> const & toolboxes, SPDesktop *desktop ) +{ +//static map<SPDesktop*, vector<GtkWidget*> > trackedBoxes; + + for (vector<GtkWidget*>::const_iterator it = toolboxes.begin(); it != toolboxes.end(); ++it ) { + GtkWidget* toolbox = *it; + + ToolboxFactory::setToolboxDesktop( toolbox, desktop ); + vector<GtkWidget*>& tracked = trackedBoxes[desktop]; + if (find(tracked.begin(), tracked.end(), toolbox) == tracked.end()) { + tracked.push_back(toolbox); + } + } + + if (std::find(desktops.begin(), desktops.end(), desktop) == desktops.end()) { + desktops.push_back(desktop); + } +} + + +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/uxmanager.h b/src/ui/uxmanager.h new file mode 100644 index 000000000..aecda2b5e --- /dev/null +++ b/src/ui/uxmanager.h @@ -0,0 +1,65 @@ +#ifndef SEEN_UI_UXMANAGER_H +#define SEEN_UI_UXMANAGER_H +/* + * A simple interface for previewing representations. + * + * Authors: + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2010 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include <vector> + +extern "C" +{ + typedef struct _GObject GObject; + typedef struct _GtkWidget GtkWidget; +} + +class SPDesktop; + +struct SPDesktopWidget; + + +namespace Inkscape { +namespace UI { + +class UXManager +{ +public: + static UXManager* getInstance(); + virtual ~UXManager(); + + void addTrack( SPDesktopWidget* dtw ); + void delTrack( SPDesktopWidget* dtw ); + + void connectToDesktop( std::vector<GtkWidget *> const & toolboxes, SPDesktop *desktop ); + + void setTask(SPDesktop* dt, gint val); + + bool isFloatWindowProblem() const; + +private: + UXManager(); + + bool floatwindowIssues; +}; + +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_UI_UXMANAGER_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/widget/page-sizer.cpp b/src/ui/widget/page-sizer.cpp index 68f26792a..e604a24ec 100644 --- a/src/ui/widget/page-sizer.cpp +++ b/src/ui/widget/page-sizer.cpp @@ -229,6 +229,11 @@ PageSizer::PageSizer(Registry & _wr) _dimensionUnits( _("U_nits:"), "units", _wr ), _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ), _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ), + _marginTop( _("T_op margin:"), _("Top margin"), "fit-margin-top", _wr ), + _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr), + _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr), + _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr), + _widgetRegistry(&_wr) { //# Set up the Paper Size combo box @@ -273,16 +278,11 @@ PageSizer::PageSizer(Registry & _wr) // _paperSizeListSelection->select(iter); - pack_start (_paperSizeListBox, true, true, 0); - _paperSizeListLabel.set_label(_("P_age size:")); - _paperSizeListLabel.set_use_underline(); - _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0); - _paperSizeListLabel.set_mnemonic_widget (_paperSizeList); - _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0); + pack_start (_paperSizeListScroller, true, true, 0); //## Set up orientation radio buttons pack_start (_orientationBox, false, false, 0); - _orientationLabel.set_label(_("Page orientation:")); + _orientationLabel.set_label(_("Orientation:")); _orientationBox.pack_start(_orientationLabel, false, false, 0); _landscapeButton.set_use_underline(); _landscapeButton.set_label(_("_Landscape")); @@ -299,19 +299,48 @@ PageSizer::PageSizer(Registry & _wr) //## Set up custom size frame _customFrame.set_label(_("Custom size")); pack_start (_customFrame, false, false, 0); - _customTable.resize(2, 2); - _customTable.set_border_width (4); - _customTable.set_row_spacings (4); - _customTable.set_col_spacings (4); - _customTable.attach(_dimensionWidth, 0,1,0,1); - _customTable.attach(_dimensionUnits, 1,2,0,1); - _customTable.attach(_dimensionHeight, 0,1,1,2); - _customTable.attach(_fitPageButton, 1,2,1,2); - _customFrame.add(_customTable); - + _customFrame.add(_customDimTable); + + _customDimTable.resize(3, 2); + _customDimTable.set_border_width(4); + _customDimTable.set_row_spacings(4); + _customDimTable.set_col_spacings(4); + _customDimTable.attach(_dimensionWidth, 0,1, 0,1); + _customDimTable.attach(_dimensionUnits, 1,2, 0,1); + _customDimTable.attach(_dimensionHeight, 0,1, 1,2); + _customDimTable.attach(_fitPageMarginExpander, 0,2, 2,3); + + //## Set up fit page expander + _fitPageMarginExpander.set_label(_("Resi_ze page to content...")); + _fitPageMarginExpander.set_use_underline(); + _fitPageMarginExpander.add(_marginTable); + + //## Set up margin settings + _marginTable.resize(4, 2); + _marginTable.set_border_width(4); + _marginTable.set_row_spacings(4); + _marginTable.set_col_spacings(4); + _marginTable.attach(_fitPageButtonAlign, 0,2, 0,1); + _marginTable.attach(_marginTopAlign, 0,2, 1,2); + _marginTable.attach(_marginLeftAlign, 0,1, 2,3); + _marginTable.attach(_marginRightAlign, 1,2, 2,3); + _marginTable.attach(_marginBottomAlign, 0,2, 3,4); + + _marginTopAlign.set(0.5, 0.5, 0.0, 1.0); + _marginTopAlign.add(_marginTop); + _marginLeftAlign.set(0.0, 0.5, 0.0, 1.0); + _marginLeftAlign.add(_marginLeft); + _marginRightAlign.set(1.0, 0.5, 0.0, 1.0); + _marginRightAlign.add(_marginRight); + _marginBottomAlign.set(0.5, 0.5, 0.0, 1.0); + _marginBottomAlign.add(_marginBottom); + + _fitPageButtonAlign.set(0.5, 0.5, 0.0, 1.0); + _fitPageButtonAlign.add(_fitPageButton); _fitPageButton.set_use_underline(); - _fitPageButton.set_label(_("_Fit page to selection")); + _fitPageButton.set_label(_("_Resize page to drawing or selection")); _tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection")); + } @@ -343,7 +372,7 @@ PageSizer::init () /** * Set document dimensions (if not called by Doc prop's update()) and * set the PageSizer's widgets and text entries accordingly. If - * 'chageList' is true, then adjust the paperSizeList to show the closest + * 'changeList' is true, then adjust the paperSizeList to show the closest * standard page size. * * \param w, h given in px @@ -454,7 +483,7 @@ PageSizer::find_paper_size (double w, double h) const /** - * Tell the desktop to change the page size + * Tell the desktop to fit the page size to the selection or drawing. */ void PageSizer::fire_fit_canvas_to_selection_or_drawing() diff --git a/src/ui/widget/page-sizer.h b/src/ui/widget/page-sizer.h index f970afe44..718eb95b5 100644 --- a/src/ui/widget/page-sizer.h +++ b/src/ui/widget/page-sizer.h @@ -183,24 +183,38 @@ protected: Gtk::HBox _orientationBox; Gtk::Label _orientationLabel; Gtk::RadioButton _portraitButton; - Gtk::RadioButton _landscapeButton; + Gtk::RadioButton _landscapeButton; //callbacks void on_portrait(); void on_landscape(); sigc::connection _portrait_connection; - sigc::connection _landscape_connection; + sigc::connection _landscape_connection; //### Custom size frame Gtk::Frame _customFrame; - Gtk::Table _customTable; + Gtk::Table _customDimTable; RegisteredUnitMenu _dimensionUnits; RegisteredScalarUnit _dimensionWidth; - RegisteredScalarUnit _dimensionHeight; - Gtk::Button _fitPageButton; + RegisteredScalarUnit _dimensionHeight; + + //### Fit Page options + Gtk::Expander _fitPageMarginExpander; + Gtk::Table _marginTable; + Gtk::Alignment _marginTopAlign; + Gtk::Alignment _marginLeftAlign; + Gtk::Alignment _marginRightAlign; + Gtk::Alignment _marginBottomAlign; + RegisteredScalar _marginTop; + RegisteredScalar _marginLeft; + RegisteredScalar _marginRight; + RegisteredScalar _marginBottom; + Gtk::Alignment _fitPageButtonAlign; + Gtk::Button _fitPageButton; + //callback void on_value_changed(); sigc::connection _changedw_connection; - sigc::connection _changedh_connection; + sigc::connection _changedh_connection; Registry *_widgetRegistry; diff --git a/src/ui/widget/spin-slider.cpp b/src/ui/widget/spin-slider.cpp index b610c1ee6..e3e73a51f 100644 --- a/src/ui/widget/spin-slider.cpp +++ b/src/ui/widget/spin-slider.cpp @@ -3,7 +3,7 @@ * * Author: * Nicholas Bishop <nicholasbishop@gmail.com> - * Felipe C. da S. Sanches <felipe.sanches@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> * * Copyright (C) 2007 Author * diff --git a/src/util/Makefile_insert b/src/util/Makefile_insert index b76a4dcdb..87a974768 100644 --- a/src/util/Makefile_insert +++ b/src/util/Makefile_insert @@ -4,6 +4,8 @@ ink_common_sources += \ util/compose.hpp \ util/ucompose.hpp \ util/enums.h \ + util/ege-tags.h \ + util/ege-tags.cpp \ util/filter-list.h \ util/fixed_point.h \ util/format.h \ diff --git a/src/util/ege-tags.cpp b/src/util/ege-tags.cpp new file mode 100644 index 000000000..5d33c85a3 --- /dev/null +++ b/src/util/ege-tags.cpp @@ -0,0 +1,178 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is EGE Tagging Support. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_LIBINTL_H +#include <libintl.h> +#endif // HAVE_LIBINTL_H + +#if !defined(_) +#define _(s) gettext(s) +#endif // !defined(_) + +#include <set> +#include <algorithm> +#include <functional> + +#include "ege-tags.h" + +#include <glib.h> + +namespace ege +{ + +Label::Label(std::string const& lang, std::string const& value) : + lang(lang), + value(value) +{ +} + +Label::~Label() +{ +} + +// ========================================================================= + +Tag::~Tag() +{ +} + +Tag::Tag(std::string const& key) : + key(key) +{ +} + +// ========================================================================= + +TagSet::TagSet() : + lang(), + tags(), + counts() +{ +} + +TagSet::~TagSet() +{ +} + +void TagSet::setLang(std::string const& lang) +{ + if (lang != this->lang) { + this->lang = lang; + } +} + + +struct sameLang : public std::binary_function<Label, Label, bool> { + bool operator()(Label const& x, Label const& y) const { return (x.lang == y.lang); } +}; + + +bool TagSet::addTag(Tag const& tag) +{ + bool present = false; + + for ( std::vector<Tag>::iterator it = tags.begin(); (it != tags.end()) && !present; ++it ) { + if (tag.key == it->key) { + present = true; + + for ( std::vector<Label>::const_iterator it2 = tag.labels.begin(); it2 != tag.labels.end(); ++it2 ) { + std::vector<Label>::iterator itOld = std::find_if( it->labels.begin(), it->labels.end(), std::bind2nd(sameLang(), *it2) ); + if (itOld != it->labels.end()) { + itOld->value = it2->value; + } else { + it->labels.push_back(*it2); + } + } + } + } + + if (!present) { + tags.push_back(tag); + counts[tag.key] = 0; + } + + return present; +} + + +std::vector<Tag> const& TagSet::getTags() +{ + return tags; +} + +int TagSet::getCount( std::string const& key ) +{ + int count = 0; + if ( counts.find(key) != counts.end() ) { + count = counts[key]; + } + return count; +} + +void TagSet::increment( std::string const& key ) +{ + if ( counts.find(key) != counts.end() ) { + counts[key]++; + } else { + Tag tag(key); + tags.push_back(tag); + counts[key] = 1; + } +} + +void TagSet::decrement( std::string const& key ) +{ + if ( counts.find(key) != counts.end() ) { + counts[key]--; + } +} + +} // namespace ege + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/util/ege-tags.h b/src/util/ege-tags.h new file mode 100644 index 000000000..eaba6a00a --- /dev/null +++ b/src/util/ege-tags.h @@ -0,0 +1,121 @@ +#ifndef SEEN_EGE_TAGS_H +#define SEEN_EGE_TAGS_H + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is EGE Tagging Support. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <string> +#include <vector> +#include <map> + +/* + * Implements base tagging of http://create.freedesktop.org/wiki/ResourceTagging . + */ + +// Note that this API is preliminary and subject to frequent change: + +namespace ege +{ + +class Label +{ +public: + Label(); + Label(std::string const& lang, std::string const& value); + ~Label(); + + std::string lang; + std::string value; +}; + +class Tag +{ +public: + Tag(); + Tag(std::string const& key); + ~Tag(); + + std::string key; + std::vector<Label> labels; +}; + + +/** + * Contains a set of tags with unique keys, and with locale support. + * + */ +class TagSet +{ +public: + TagSet(); + ~TagSet(); + + std::string const & getLang() const; + void setLang(std::string const& lang); + + /** + * Adds or updates a tag. + * + * @return true if a tag was updated, false if it was added. + */ + bool addTag(Tag const& tag); + std::vector<Tag> const& getTags(); + + int getCount( std::string const& key ); + void increment( std::string const& key ); + void decrement( std::string const& key ); + +private: + + std::string lang; + std::vector<Tag> tags; + std::map<std::string, int> counts; +}; + +} // namespace ege + + +#endif // SEEN_EGE_TAGS_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index ab46b21a6..78ceec467 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -103,7 +103,7 @@ vp_knot_moved_handler (SPKnot */*knot*/, Geom::Point const *ppointer, guint stat sel_boxes = (*vp)->selectedBoxes(sp_desktop_selection(inkscape_active_desktop())); // we create a new perspective ... - Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp); + Persp3D *new_persp = persp3d_create_xml_element (dragger->parent->document, old_persp->perspective_impl); /* ... unlink the boxes from the old one and FIXME: We need to unlink the _un_selected boxes of each VP so that @@ -230,7 +230,7 @@ unsigned int VanishingPoint::global_counter = 0; void VanishingPoint::set_pos(Proj::Pt2 const &pt) { g_return_if_fail (_persp); - _persp->tmat.set_image_pt (_axis, pt); + _persp->perspective_impl->tmat.set_image_pt (_axis, pt); } std::list<SPBox3D *> diff --git a/src/verbs.cpp b/src/verbs.cpp index f03be681a..37f4da4d6 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -1450,6 +1450,9 @@ ContextVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_CONTEXT_TWEAK: tools_switch(dt, TOOLS_TWEAK); break; + case SP_VERB_CONTEXT_SPRAY: + tools_switch(dt, TOOLS_SPRAY); + break; case SP_VERB_CONTEXT_RECT: tools_switch(dt, TOOLS_SHAPES_RECT); break; @@ -1511,6 +1514,10 @@ ContextVerb::perform(SPAction *action, void *data, void */*pdata*/) prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_TWEAK); dt->_dlg_mgr->showDialog("InkscapePreferences"); break; + case SP_VERB_CONTEXT_SPRAY_PREFS: + prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_SPRAY); + dt->_dlg_mgr->showDialog("InkscapePreferences"); + break; case SP_VERB_CONTEXT_RECT_PREFS: prefs->setInt("/dialogs/preferences/page", PREFS_PAGE_TOOLS_SHAPES_RECT); dt->_dlg_mgr->showDialog("InkscapePreferences"); @@ -1728,6 +1735,9 @@ ZoomVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_VIEW_MODE_OUTLINE: dt->setDisplayModeOutline(); break; + case SP_VERB_VIEW_MODE_PRINT_COLORS_PREVIEW: + dt->setDisplayModePrintColorsPreview(); + break; case SP_VERB_VIEW_MODE_TOGGLE: dt->displayModeToggle(); break; @@ -1783,6 +1793,9 @@ DialogVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_DIALOG_ALIGN_DISTRIBUTE: dt->_dlg_mgr->showDialog("AlignAndDistribute"); break; + case SP_VERB_DIALOG_SPRAY_OPTION: + dt->_dlg_mgr->showDialog("SprayOptionClass"); + break; case SP_VERB_DIALOG_TEXT: sp_text_edit_dialog(); break; @@ -1847,6 +1860,9 @@ DialogVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_DIALOG_SVG_FONTS: dt->_dlg_mgr->showDialog("SvgFontsDialog"); break; + case SP_VERB_DIALOG_PRINT_COLORS_PREVIEW: + dt->_dlg_mgr->showDialog("PrintColorsPreviewDialog"); + break; default: break; } @@ -2489,6 +2505,8 @@ Verb *Verb::_base_verbs[] = { N_("Edit paths by nodes"), INKSCAPE_ICON_TOOL_NODE_EDITOR), new ContextVerb(SP_VERB_CONTEXT_TWEAK, "ToolTweak", N_("Tweak"), N_("Tweak objects by sculpting or painting"), INKSCAPE_ICON_TOOL_TWEAK), + new ContextVerb(SP_VERB_CONTEXT_SPRAY, "ToolSpray", N_("Spray"), + N_("Spray objects by sculpting or painting"), INKSCAPE_ICON_TOOL_SPRAY), new ContextVerb(SP_VERB_CONTEXT_RECT, "ToolRect", N_("Rectangle"), N_("Create rectangles and squares"), INKSCAPE_ICON_DRAW_RECTANGLE), new ContextVerb(SP_VERB_CONTEXT_3DBOX, "Tool3DBox", N_("3D Box"), @@ -2530,6 +2548,8 @@ Verb *Verb::_base_verbs[] = { N_("Open Preferences for the Node tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_TWEAK_PREFS, "TweakPrefs", N_("Tweak Tool Preferences"), N_("Open Preferences for the Tweak tool"), NULL), + new ContextVerb(SP_VERB_CONTEXT_SPRAY_PREFS, "SprayPrefs", N_("Spray Tool Preferences"), + N_("Open Preferences for the Spray tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_RECT_PREFS, "RectPrefs", N_("Rectangle Preferences"), N_("Open Preferences for the Rectangle tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_3DBOX_PREFS, "3DBoxPrefs", N_("3D Box Preferences"), @@ -2598,6 +2618,8 @@ Verb *Verb::_base_verbs[] = { N_("Switch to normal display without filters"), NULL), new ZoomVerb(SP_VERB_VIEW_MODE_OUTLINE, "ViewModeOutline", N_("_Outline"), N_("Switch to outline (wireframe) display mode"), NULL), + new ZoomVerb(SP_VERB_VIEW_MODE_PRINT_COLORS_PREVIEW, "ViewModePrintColorsPreview", N_("_Print Colors Preview"), + N_("Switch to print colors preview mode"), NULL), new ZoomVerb(SP_VERB_VIEW_MODE_TOGGLE, "ViewModeToggle", N_("_Toggle"), N_("Toggle between normal and outline display modes"), NULL), @@ -2631,6 +2653,8 @@ Verb *Verb::_base_verbs[] = { N_("Precisely control objects' transformations"), INKSCAPE_ICON_DIALOG_TRANSFORM), new DialogVerb(SP_VERB_DIALOG_ALIGN_DISTRIBUTE, "DialogAlignDistribute", N_("_Align and Distribute..."), N_("Align and distribute objects"), INKSCAPE_ICON_DIALOG_ALIGN_AND_DISTRIBUTE), + new DialogVerb(SP_VERB_DIALOG_SPRAY_OPTION, "DialogSprayOption", N_("_Spray options..."), + N_("Some options for the spray"), INKSCAPE_ICON_DIALOG_SPRAY_OPTIONS), new DialogVerb(SP_VERB_DIALOG_UNDO_HISTORY, "DialogUndoHistory", N_("Undo _History..."), N_("Undo History"), INKSCAPE_ICON_EDIT_UNDO_HISTORY), new DialogVerb(SP_VERB_DIALOG_TEXT, "DialogText", N_("_Text and Font..."), @@ -2671,6 +2695,8 @@ Verb *Verb::_base_verbs[] = { N_("Manage, edit, and apply SVG filters"), NULL), new DialogVerb(SP_VERB_DIALOG_SVG_FONTS, "DialogSVGFonts", N_("SVG Font Editor..."), N_("Edit SVG fonts"), NULL), + new DialogVerb(SP_VERB_DIALOG_PRINT_COLORS_PREVIEW, "DialogPrintColorsPreview", N_("Print Colors..."), + N_("Select which color separations to render in Print Colors Preview rendermode"), NULL), /* Help */ new HelpVerb(SP_VERB_HELP_ABOUT_EXTENSIONS, "HelpAboutExtensions", N_("About E_xtensions"), diff --git a/src/verbs.h b/src/verbs.h index 87fe27075..d0abcdca2 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -153,6 +153,7 @@ enum { SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_TWEAK, + SP_VERB_CONTEXT_SPRAY, SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_ARC, @@ -174,6 +175,7 @@ enum { SP_VERB_CONTEXT_SELECT_PREFS, SP_VERB_CONTEXT_NODE_PREFS, SP_VERB_CONTEXT_TWEAK_PREFS, + SP_VERB_CONTEXT_SPRAY_PREFS, SP_VERB_CONTEXT_RECT_PREFS, SP_VERB_CONTEXT_3DBOX_PREFS, SP_VERB_CONTEXT_ARC_PREFS, @@ -212,6 +214,7 @@ enum { SP_VERB_VIEW_MODE_NORMAL, SP_VERB_VIEW_MODE_NO_FILTERS, SP_VERB_VIEW_MODE_OUTLINE, + SP_VERB_VIEW_MODE_PRINT_COLORS_PREVIEW, SP_VERB_VIEW_MODE_TOGGLE, SP_VERB_VIEW_CMS_TOGGLE, SP_VERB_VIEW_ICON_PREVIEW, @@ -227,6 +230,7 @@ enum { SP_VERB_DIALOG_SWATCHES, SP_VERB_DIALOG_TRANSFORM, SP_VERB_DIALOG_ALIGN_DISTRIBUTE, + SP_VERB_DIALOG_SPRAY_OPTION, SP_VERB_DIALOG_UNDO_HISTORY, SP_VERB_DIALOG_TEXT, SP_VERB_DIALOG_XML_EDITOR, @@ -248,6 +252,7 @@ enum { SP_VERB_DIALOG_LIVE_PATH_EFFECT, SP_VERB_DIALOG_FILTER_EFFECTS, SP_VERB_DIALOG_SVG_FONTS, + SP_VERB_DIALOG_PRINT_COLORS_PREVIEW, /* Help */ SP_VERB_HELP_ABOUT_EXTENSIONS, SP_VERB_HELP_MEMORY, diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp index e3bf1ae9c..32d682904 100644 --- a/src/widgets/desktop-widget.cpp +++ b/src/widgets/desktop-widget.cpp @@ -48,25 +48,31 @@ #include "sp-image.h" #include "sp-item.h" #include "sp-namedview.h" -#include "toolbox.h" #include "ui/dialog/dialog-manager.h" #include "ui/dialog/swatches.h" #include "ui/icon-names.h" #include "ui/widget/dock.h" #include "ui/widget/layer-selector.h" #include "ui/widget/selected-style.h" -#include "widgets/button.h" -#include "widgets/ruler.h" -#include "widgets/spinbutton-events.h" -#include "widgets/spw-utilities.h" -#include "widgets/toolbox.h" -#include "widgets/widget-sizes.h" +#include "ui/uxmanager.h" + +// We're in the "widgets" directory, so no need to explicitly prefix these: +#include "button.h" +#include "ruler.h" +#include "spinbutton-events.h" +#include "spw-utilities.h" +#include "toolbox.h" +#include "widget-sizes.h" #if defined (SOLARIS) && (SOLARIS == 8) #include "round.h" using Inkscape::round; #endif + +using Inkscape::UI::UXManager; +using Inkscape::UI::ToolboxFactory; + #ifdef WITH_INKBOARD #endif @@ -85,7 +91,6 @@ enum { /* SPDesktopWidget */ static void sp_desktop_widget_class_init (SPDesktopWidgetClass *klass); -static void sp_desktop_widget_init (SPDesktopWidget *widget); static void sp_desktop_widget_destroy (GtkObject *object); static void sp_desktop_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation); @@ -97,7 +102,6 @@ static void sp_dtw_color_profile_event(EgeColorProfTracker *widget, SPDesktopWid static void cms_adjust_toggled( GtkWidget *button, gpointer data ); static void cms_adjust_set_sensitive( SPDesktopWidget *dtw, bool enabled ); static void sp_desktop_widget_adjustment_value_changed (GtkAdjustment *adj, SPDesktopWidget *dtw); -static void sp_desktop_widget_namedview_modified (SPObject *obj, guint flags, SPDesktopWidget *dtw); static gdouble sp_dtw_zoom_value_to_display (gdouble value); static gdouble sp_dtw_zoom_display_to_value (gdouble value); @@ -241,7 +245,7 @@ SPDesktopWidget::window_get_pointer() /** * Registers SPDesktopWidget class and returns its type number. */ -GType sp_desktop_widget_get_type(void) +GType SPDesktopWidget::getType(void) { static GtkType type = 0; if (!type) { @@ -254,7 +258,7 @@ GType sp_desktop_widget_get_type(void) 0, // class_data sizeof(SPDesktopWidget), 0, // n_preallocs - (GInstanceInitFunc)sp_desktop_widget_init, + (GInstanceInitFunc)SPDesktopWidget::init, 0 // value_table }; type = g_type_register_static(SP_TYPE_VIEW_WIDGET, "SPDesktopWidget", &info, static_cast<GTypeFlags>(0)); @@ -282,14 +286,12 @@ sp_desktop_widget_class_init (SPDesktopWidgetClass *klass) /** * Callback for SPDesktopWidget object initialization. */ -static void -sp_desktop_widget_init (SPDesktopWidget *dtw) +void SPDesktopWidget::init( SPDesktopWidget *dtw ) { GtkWidget *widget; GtkWidget *tbl; GtkWidget *canvas_tbl; - GtkWidget *hbox; GtkWidget *eventbox; GtkStyle *style; @@ -320,24 +322,26 @@ sp_desktop_widget_init (SPDesktopWidget *dtw) gtk_box_pack_end( GTK_BOX( dtw->vbox ), GTK_WIDGET(dtw->panels->gobj()), FALSE, TRUE, 0 ); } - hbox = gtk_hbox_new (FALSE, 0); - gtk_box_pack_end (GTK_BOX (dtw->vbox), hbox, TRUE, TRUE, 0); - gtk_widget_show (hbox); + dtw->hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end( GTK_BOX (dtw->vbox), dtw->hbox, TRUE, TRUE, 0 ); + gtk_widget_show(dtw->hbox); - dtw->aux_toolbox = sp_aux_toolbox_new (); + dtw->aux_toolbox = ToolboxFactory::createAuxToolbox(); gtk_box_pack_end (GTK_BOX (dtw->vbox), dtw->aux_toolbox, FALSE, TRUE, 0); - dtw->snap_toolbox = sp_snap_toolbox_new (); - gtk_box_pack_end (GTK_BOX (dtw->vbox), dtw->snap_toolbox, FALSE, TRUE, 0); + dtw->snap_toolbox = ToolboxFactory::createSnapToolbox(); + ToolboxFactory::setOrientation( dtw->snap_toolbox, GTK_ORIENTATION_VERTICAL ); + gtk_box_pack_end( GTK_BOX(dtw->hbox), dtw->snap_toolbox, FALSE, TRUE, 0 ); - dtw->commands_toolbox = sp_commands_toolbox_new (); + dtw->commands_toolbox = ToolboxFactory::createCommandsToolbox(); gtk_box_pack_end (GTK_BOX (dtw->vbox), dtw->commands_toolbox, FALSE, TRUE, 0); - dtw->tool_toolbox = sp_tool_toolbox_new (); - gtk_box_pack_start (GTK_BOX (hbox), dtw->tool_toolbox, FALSE, TRUE, 0); + dtw->tool_toolbox = ToolboxFactory::createToolToolbox(); + ToolboxFactory::setOrientation( dtw->tool_toolbox, GTK_ORIENTATION_VERTICAL ); + gtk_box_pack_start( GTK_BOX(dtw->hbox), dtw->tool_toolbox, FALSE, TRUE, 0 ); tbl = gtk_table_new (2, 3, FALSE); - gtk_box_pack_start (GTK_BOX (hbox), tbl, TRUE, TRUE, 1); + gtk_box_pack_start( GTK_BOX(dtw->hbox), tbl, TRUE, TRUE, 1 ); canvas_tbl = gtk_table_new (3, 3, FALSE); @@ -567,6 +571,8 @@ sp_desktop_widget_destroy (GtkObject *object) { SPDesktopWidget *dtw = SP_DESKTOP_WIDGET (object); + UXManager::getInstance()->delTrack(dtw); + if (dtw->desktop) { if ( watcher ) { watcher->remove(dtw); @@ -613,12 +619,20 @@ SPDesktopWidget::updateTitle(gchar const* uri) if (this->desktop->number > 1) { if (this->desktop->getMode() == Inkscape::RENDERMODE_OUTLINE) { g_string_printf (name, _("%s: %d (outline) - Inkscape"), fname, this->desktop->number); + } else if (this->desktop->getMode() == Inkscape::RENDERMODE_NO_FILTERS) { + g_string_printf (name, _("%s: %d (no filters) - Inkscape"), fname, this->desktop->number); + } else if (this->desktop->getMode() == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW) { + g_string_printf (name, _("%s: %d (print colors preview) - Inkscape"), fname, this->desktop->number); } else { g_string_printf (name, _("%s: %d - Inkscape"), fname, this->desktop->number); } } else { if (this->desktop->getMode() == Inkscape::RENDERMODE_OUTLINE) { g_string_printf (name, _("%s (outline) - Inkscape"), fname); + } else if (this->desktop->getMode() == Inkscape::RENDERMODE_NO_FILTERS) { + g_string_printf (name, _("%s (no filters) - Inkscape"), fname); + } else if (this->desktop->getMode() == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW) { + g_string_printf (name, _("%s (print colors preview) - Inkscape"), fname); } else { g_string_printf (name, _("%s - Inkscape"), fname); } @@ -699,23 +713,22 @@ sp_desktop_widget_realize (GtkWidget *widget) dtw->desktop->set_display_area (d.x0, d.y0, d.x1, d.y1, 10); - sp_desktop_widget_update_namedview(dtw); + dtw->updateNamedview(); } /* This is just to provide access to common functionality from sp_desktop_widget_realize() above as well as from SPDesktop::change_document() */ -void -sp_desktop_widget_update_namedview (SPDesktopWidget *dtw) { - g_return_if_fail(dtw); - - /* Listen on namedview modification */ +void SPDesktopWidget::updateNamedview() +{ + // Listen on namedview modification // originally (prior to the sigc++ conversion) the signal was simply // connected twice rather than disconnecting the first connection - dtw->modified_connection.disconnect(); - dtw->modified_connection = dtw->desktop->namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_desktop_widget_namedview_modified), dtw)); - sp_desktop_widget_namedview_modified (dtw->desktop->namedview, SP_OBJECT_MODIFIED_FLAG, dtw); + modified_connection.disconnect(); + + modified_connection = desktop->namedview->connectModified(sigc::mem_fun(*this, &SPDesktopWidget::namedviewModified)); + namedviewModified(desktop->namedview, SP_OBJECT_MODIFIED_FLAG); - dtw->updateTitle(SP_DOCUMENT_NAME (dtw->desktop->doc())); + updateTitle(SP_DOCUMENT_NAME (desktop->doc())); } /** @@ -1191,18 +1204,18 @@ sp_desktop_widget_fullscreen(SPDesktopWidget *dtw) /** * Hide whatever the user does not want to see in the window */ -void -sp_desktop_widget_layout (SPDesktopWidget *dtw) +void SPDesktopWidget::layoutWidgets() { + SPDesktopWidget *dtw = this; Glib::ustring pref_root; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (dtw->desktop->is_focusMode()) { - pref_root = "/focus/"; + pref_root = "/focus/"; } else if (dtw->desktop->is_fullscreen()) { - pref_root = "/fullscreen/"; + pref_root = "/fullscreen/"; } else { - pref_root = "/window/"; + pref_root = "/window/"; } #ifndef GDK_WINDOWING_QUARTZ @@ -1220,17 +1233,17 @@ sp_desktop_widget_layout (SPDesktopWidget *dtw) } if (!prefs->getBool(pref_root + "snaptoolbox/state", true)) { - gtk_widget_hide_all (dtw->snap_toolbox); - } else { - gtk_widget_show_all (dtw->snap_toolbox); - } + gtk_widget_hide_all (dtw->snap_toolbox); + } else { + gtk_widget_show_all (dtw->snap_toolbox); + } if (!prefs->getBool(pref_root + "toppanel/state", true)) { gtk_widget_hide_all (dtw->aux_toolbox); } else { // we cannot just show_all because that will show all tools' panels; // this is a function from toolbox.cpp that shows only the current tool's panel - show_aux_toolbox (dtw->aux_toolbox); + ToolboxFactory::showAuxToolbox(dtw->aux_toolbox); } if (!prefs->getBool(pref_root + "toolbox/state", true)) { @@ -1328,8 +1341,57 @@ SPDesktopWidget::isToolboxButtonActive (const gchar* id) return isActive; } -SPViewWidget * -sp_desktop_widget_new (SPNamedView *namedview) +void SPDesktopWidget::setToolboxPosition(Glib::ustring const& id, GtkPositionType pos) +{ + // Note - later on these won't be individual member variables. + GtkWidget* toolbox = 0; + if (id == "ToolToolbar") { + toolbox = tool_toolbox; + } else if (id == "AuxToolbar") { + toolbox = aux_toolbox; + } else if (id == "CommandsToolbar") { + toolbox = commands_toolbox; + } else if (id == "SnapToolbar") { + toolbox = snap_toolbox; + } + + + if (toolbox) { + switch(pos) { + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + if ( gtk_widget_is_ancestor(toolbox, hbox) ) { + gtk_widget_reparent( toolbox, vbox ); + gtk_box_set_child_packing(GTK_BOX(vbox), toolbox, FALSE, TRUE, 0, GTK_PACK_START); + } + ToolboxFactory::setOrientation(toolbox, GTK_ORIENTATION_HORIZONTAL); + break; + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + if ( !gtk_widget_is_ancestor(toolbox, hbox) ) { + gtk_widget_reparent( toolbox, hbox ); + gtk_box_set_child_packing(GTK_BOX(hbox), toolbox, FALSE, TRUE, 0, GTK_PACK_START); + if (pos == GTK_POS_LEFT) { + gtk_box_reorder_child( GTK_BOX(hbox), toolbox, 0 ); + } + } + ToolboxFactory::setOrientation(toolbox, GTK_ORIENTATION_VERTICAL); + break; + } + } +} + + +SPViewWidget *sp_desktop_widget_new( SPNamedView *namedview ) +{ + SPDesktopWidget* dtw = SPDesktopWidget::createInstance(namedview); + + UXManager::getInstance()->addTrack(dtw); + + return SP_VIEW_WIDGET(dtw); +} + +SPDesktopWidget* SPDesktopWidget::createInstance(SPNamedView *namedview) { SPDesktopWidget *dtw = (SPDesktopWidget*)g_object_new(SP_TYPE_DESKTOP_WIDGET, NULL); @@ -1354,7 +1416,7 @@ sp_desktop_widget_new (SPNamedView *namedview) sp_view_widget_set_view (SP_VIEW_WIDGET (dtw), dtw->desktop); /* Listen on namedview modification */ - dtw->modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_desktop_widget_namedview_modified), dtw)); + dtw->modified_connection = namedview->connectModified(sigc::mem_fun(*dtw, &SPDesktopWidget::namedviewModified)); dtw->layer_selector->setDesktop(dtw->desktop); @@ -1364,16 +1426,18 @@ sp_desktop_widget_new (SPNamedView *namedview) gtk_box_pack_start (GTK_BOX (dtw->vbox), dtw->menubar, FALSE, FALSE, 0); #endif - sp_desktop_widget_layout (dtw); + dtw->layoutWidgets(); - sp_tool_toolbox_set_desktop (dtw->tool_toolbox, dtw->desktop); - sp_aux_toolbox_set_desktop (dtw->aux_toolbox, dtw->desktop); - sp_commands_toolbox_set_desktop (dtw->commands_toolbox, dtw->desktop); - sp_snap_toolbox_set_desktop (dtw->snap_toolbox, dtw->desktop); + std::vector<GtkWidget *> toolboxes; + toolboxes.push_back(dtw->tool_toolbox); + toolboxes.push_back(dtw->aux_toolbox); + toolboxes.push_back(dtw->commands_toolbox); + toolboxes.push_back(dtw->snap_toolbox); + UXManager::getInstance()->connectToDesktop( toolboxes, dtw->desktop ); dtw->panels->setDesktop( dtw->desktop ); - return SP_VIEW_WIDGET (dtw); + return dtw; } void @@ -1428,22 +1492,22 @@ sp_desktop_widget_update_vruler (SPDesktopWidget *dtw) } -static void -sp_desktop_widget_namedview_modified (SPObject *obj, guint flags, SPDesktopWidget *dtw) +void SPDesktopWidget::namedviewModified(SPObject *obj, guint flags) { SPNamedView *nv=SP_NAMEDVIEW(obj); + if (flags & SP_OBJECT_MODIFIED_FLAG) { - dtw->dt2r = 1.0 / nv->doc_units->unittobase; - dtw->ruler_origin = Geom::Point(0,0); //nv->gridorigin; Why was the grid origin used here? + this->dt2r = 1.0 / nv->doc_units->unittobase; + this->ruler_origin = Geom::Point(0,0); //nv->gridorigin; Why was the grid origin used here? - sp_ruler_set_metric (GTK_RULER (dtw->vruler), nv->getDefaultMetric()); - sp_ruler_set_metric (GTK_RULER (dtw->hruler), nv->getDefaultMetric()); + sp_ruler_set_metric(GTK_RULER (this->vruler), nv->getDefaultMetric()); + sp_ruler_set_metric(GTK_RULER (this->hruler), nv->getDefaultMetric()); - gtk_tooltips_set_tip (dtw->tt, dtw->hruler_box, gettext(sp_unit_get_plural (nv->doc_units)), NULL); - gtk_tooltips_set_tip (dtw->tt, dtw->vruler_box, gettext(sp_unit_get_plural (nv->doc_units)), NULL); + gtk_tooltips_set_tip(this->tt, this->hruler_box, gettext(sp_unit_get_plural (nv->doc_units)), NULL); + gtk_tooltips_set_tip(this->tt, this->vruler_box, gettext(sp_unit_get_plural (nv->doc_units)), NULL); - sp_desktop_widget_update_rulers (dtw); - update_snap_toolbox(dtw->desktop, NULL, dtw->snap_toolbox); + sp_desktop_widget_update_rulers(this); + ToolboxFactory::updateSnapToolbox(this->desktop, 0, this->snap_toolbox); } } diff --git a/src/widgets/desktop-widget.h b/src/widgets/desktop-widget.h index 04146cac6..33f2a6ae7 100644 --- a/src/widgets/desktop-widget.h +++ b/src/widgets/desktop-widget.h @@ -5,6 +5,7 @@ * SPDesktopWidget: handling Gtk events on a desktop. * * Authors: + * Jon A. Cruz <jon@joncruz.org> (c) 2010 * John Bintz <jcoswell@coswellproductions.org> (c) 2006 * Ralf Stephan <ralf@ark.in-berlin.de> (c) 2005, distrib. under GPL2 * ? -2004 @@ -26,28 +27,23 @@ typedef struct _EgeColorProfTracker EgeColorProfTracker; -#define SP_TYPE_DESKTOP_WIDGET (sp_desktop_widget_get_type ()) +#define SP_TYPE_DESKTOP_WIDGET SPDesktopWidget::getType() #define SP_DESKTOP_WIDGET(o) (GTK_CHECK_CAST ((o), SP_TYPE_DESKTOP_WIDGET, SPDesktopWidget)) #define SP_DESKTOP_WIDGET_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), SP_TYPE_DESKTOP_WIDGET, SPDesktopWidgetClass)) #define SP_IS_DESKTOP_WIDGET(o) (GTK_CHECK_TYPE ((o), SP_TYPE_DESKTOP_WIDGET)) #define SP_IS_DESKTOP_WIDGET_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), SP_TYPE_DESKTOP_WIDGET)) -GtkType sp_desktop_widget_get_type(); - void sp_desktop_widget_destroy (SPDesktopWidget* dtw); void sp_desktop_widget_show_decorations(SPDesktopWidget *dtw, gboolean show); void sp_desktop_widget_iconify(SPDesktopWidget *dtw); void sp_desktop_widget_maximize(SPDesktopWidget *dtw); void sp_desktop_widget_fullscreen(SPDesktopWidget *dtw); -void sp_desktop_widget_layout(SPDesktopWidget *dtw); void sp_desktop_widget_update_zoom(SPDesktopWidget *dtw); void sp_desktop_widget_update_rulers (SPDesktopWidget *dtw); void sp_desktop_widget_update_hruler (SPDesktopWidget *dtw); void sp_desktop_widget_update_vruler (SPDesktopWidget *dtw); -void sp_desktop_widget_update_namedview (SPDesktopWidget *dtw); - /* Show/hide rulers & scrollbars */ void sp_desktop_widget_toggle_rulers (SPDesktopWidget *dtw); void sp_desktop_widget_toggle_scrollbars (SPDesktopWidget *dtw); @@ -80,14 +76,14 @@ struct SPDesktopWidget { // The root vbox of the window layout. GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *menubar, *statusbar; Inkscape::UI::Dialogs::SwatchesPanel *panels; GtkWidget *hscrollbar, *vscrollbar, *vscrollbar_box; - GtkWidget *tool_toolbox, *aux_toolbox, *commands_toolbox, *snap_toolbox; - /* Rulers */ GtkWidget *hruler, *vruler; GtkWidget *hruler_box, *vruler_box; // eventboxes for setting tooltips @@ -129,8 +125,11 @@ struct SPDesktopWidget { { _dtw->updateTitle (uri); } virtual Gtk::Window* getWindow() { return _dtw->window; } - virtual void layout() - { sp_desktop_widget_layout (_dtw); } + + virtual void layout() { + _dtw->layoutWidgets(); + } + virtual void present() { _dtw->presentWindow(); } virtual void getGeometry (gint &x, gint &y, gint &w, gint &h) @@ -221,6 +220,7 @@ struct SPDesktopWidget { void setToolboxAdjustmentValue (gchar const * id, double value); void setToolboxSelectOneValue (gchar const * id, gint value); bool isToolboxButtonActive (gchar const *id); + void setToolboxPosition(Glib::ustring const& id, GtkPositionType pos); void setCoordinateStatus(Geom::Point p); void requestCanvasUpdate(); void requestCanvasUpdateAndWait(); @@ -231,6 +231,22 @@ struct SPDesktopWidget { Inkscape::UI::Widget::Dock* getDock(); + static GtkType getType(); + static SPDesktopWidget* createInstance(SPNamedView *namedview); + + void updateNamedview(); + +private: + GtkWidget *tool_toolbox; + GtkWidget *aux_toolbox; + GtkWidget *commands_toolbox,; + GtkWidget *snap_toolbox; + + static void init(SPDesktopWidget *widget); + void layoutWidgets(); + + void namedviewModified(SPObject *obj, guint flags); + }; /// The SPDesktopWidget vtable diff --git a/src/widgets/icon.cpp b/src/widgets/icon.cpp index 5824b102c..743502d27 100644 --- a/src/widgets/icon.cpp +++ b/src/widgets/icon.cpp @@ -714,7 +714,7 @@ int sp_icon_get_phys_size(int size) static int lastSys[Inkscape::ICON_SIZE_DECORATION + 1]; static int vals[Inkscape::ICON_SIZE_DECORATION + 1]; - size = CLAMP( size, GTK_ICON_SIZE_MENU, Inkscape::ICON_SIZE_DECORATION ); + size = CLAMP( size, static_cast<int>(GTK_ICON_SIZE_MENU), static_cast<int>(Inkscape::ICON_SIZE_DECORATION) ); if ( !sizeMapDone ) { injectCustomSize(); diff --git a/src/widgets/ruler.cpp b/src/widgets/ruler.cpp index c90b55e73..c70d96991 100644 --- a/src/widgets/ruler.cpp +++ b/src/widgets/ruler.cpp @@ -727,6 +727,7 @@ sp_vruler_size_allocate (GtkWidget *widget, GtkAllocation *allocation) // code, those warnings are actually desired. They say "Hey! Fix this". We // definitely don't want to hide/ignore them. --JonCruz +// TODO address const/non-const gchar* issue: /// Ruler metrics. static GtkRulerMetric const sp_ruler_metrics[] = { // NOTE: the order of records in this struct must correspond to the SPMetric enum. diff --git a/src/widgets/select-toolbar.cpp b/src/widgets/select-toolbar.cpp index f168cedeb..f383d2500 100644 --- a/src/widgets/select-toolbar.cpp +++ b/src/widgets/select-toolbar.cpp @@ -390,7 +390,7 @@ static GtkAction* create_action_for_verb( Inkscape::Verb* verb, Inkscape::UI::Vi void sp_select_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { Inkscape::UI::View::View *view = desktop; - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = Inkscape::UI::ToolboxFactory::prefToSize("/toolbox/secondary", 1); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); GtkAction* act = 0; diff --git a/src/widgets/sp-color-icc-selector.cpp b/src/widgets/sp-color-icc-selector.cpp index a10d2380c..3b4b6b711 100644 --- a/src/widgets/sp-color-icc-selector.cpp +++ b/src/widgets/sp-color-icc-selector.cpp @@ -17,12 +17,12 @@ #include "inkscape.h" #include "profile-manager.h" -#define noDEBUG_LCMS +#define DEBUG_LCMS #if ENABLE_LCMS #include "color-profile-fns.h" #include "color-profile.h" -//#define DEBUG_LCMS + #ifdef DEBUG_LCMS #include "preferences.h" #include <gtk/gtkmessagedialog.h> @@ -603,7 +603,7 @@ void ColorICCSelector::_profilesChanged( std::string const & name ) void ColorICCSelector::_colorChanged() { _updating = TRUE; -// sp_color_icc_set_color( SP_COLOR_ICC( _icc ), &color ); + //sp_color_icc_set_color( SP_COLOR_ICC( _icc ), &color ); #ifdef DEBUG_LCMS g_message( "/^^^^^^^^^ %p::_colorChanged(%08x:%s)", this, diff --git a/src/widgets/sp-color-notebook.cpp b/src/widgets/sp-color-notebook.cpp index 779895de4..3ba39dd30 100644 --- a/src/widgets/sp-color-notebook.cpp +++ b/src/widgets/sp-color-notebook.cpp @@ -32,6 +32,10 @@ #include "sp-color-scales.h" #include "sp-color-icc-selector.h" #include "sp-color-wheel-selector.h" +#include "svg/svg-icc-color.h" +#include "../inkscape.h" +#include "../document.h" +#include "../profile-manager.h" struct SPColorNotebookTracker { const gchar* name; @@ -324,9 +328,37 @@ void ColorNotebook::init() row++; - /* Create RGBA entry and color preview */ GtkWidget *rgbabox = gtk_hbox_new (FALSE, 0); +#if ENABLE_LCMS + /* Create color management icons */ + _box_colormanaged = gtk_event_box_new (); + GtkWidget *colormanaged = gtk_image_new_from_icon_name ("color-management-icon", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add (GTK_CONTAINER (_box_colormanaged), colormanaged); + GtkTooltips *tooltips_colormanaged = gtk_tooltips_new (); + gtk_tooltips_set_tip (tooltips_colormanaged, _box_colormanaged, _("Color Managed"), ""); + gtk_widget_set_sensitive (_box_colormanaged, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_colormanaged, FALSE, FALSE, 2); + + _box_outofgamut = gtk_event_box_new (); + GtkWidget *outofgamut = gtk_image_new_from_icon_name ("out-of-gamut-icon", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add (GTK_CONTAINER (_box_outofgamut), outofgamut); + GtkTooltips *tooltips_outofgamut = gtk_tooltips_new (); + gtk_tooltips_set_tip (tooltips_outofgamut, _box_outofgamut, _("Out of gamut!"), ""); + gtk_widget_set_sensitive (_box_outofgamut, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_outofgamut, FALSE, FALSE, 2); + + _box_toomuchink = gtk_event_box_new (); + GtkWidget *toomuchink = gtk_image_new_from_icon_name ("too-much-ink-icon", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add (GTK_CONTAINER (_box_toomuchink), toomuchink); + GtkTooltips *tooltips_toomuchink = gtk_tooltips_new (); + gtk_tooltips_set_tip (tooltips_toomuchink, _box_toomuchink, _("Too much ink!"), ""); + gtk_widget_set_sensitive (_box_toomuchink, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_toomuchink, FALSE, FALSE, 2); + +#endif //ENABLE_LCMS + + /* Create RGBA entry and color preview */ _rgbal = gtk_label_new_with_mnemonic (_("RGBA_:")); gtk_misc_set_alignment (GTK_MISC (_rgbal), 1.0, 0.5); gtk_box_pack_start(GTK_BOX(rgbabox), _rgbal, TRUE, TRUE, 2); @@ -341,7 +373,13 @@ void ColorNotebook::init() sp_set_font_size_smaller (rgbabox); gtk_widget_show_all (rgbabox); - gtk_table_attach (GTK_TABLE (table), rgbabox, 1, 2, row, row + 1, GTK_FILL, GTK_SHRINK, XPAD, YPAD); + +#if ENABLE_LCMS + //the "too much ink" icon is initially hidden + gtk_widget_hide(GTK_WIDGET(_box_toomuchink)); +#endif //ENABLE_LCMS + + gtk_table_attach (GTK_TABLE (table), rgbabox, 0, 2, row, row + 1, GTK_FILL, GTK_SHRINK, XPAD, YPAD); #ifdef SPCS_PREVIEW _p = sp_color_preview_new (0xffffffff); @@ -485,6 +523,40 @@ void ColorNotebook::_updateRgbaEntry( const SPColor& color, gfloat alpha ) { g_return_if_fail( ( 0.0 <= alpha ) && ( alpha <= 1.0 ) ); +#if ENABLE_LCMS + /* update color management icon*/ + gtk_widget_set_sensitive (_box_colormanaged, color.icc != NULL); + + /* update out-of-gamut icon */ + gtk_widget_set_sensitive (_box_outofgamut, false); + if (color.icc){ + Inkscape::ColorProfile* target_profile = SP_ACTIVE_DOCUMENT->profileManager->find(color.icc->colorProfile.c_str()); + if ( target_profile ) + gtk_widget_set_sensitive (_box_outofgamut, target_profile->GamutCheck(color)); + } + + /* update too-much-ink icon */ + gtk_widget_set_sensitive (_box_toomuchink, false); + if (color.icc){ + Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->profileManager->find(color.icc->colorProfile.c_str()); + if (prof->getColorSpace() == icSigCmykData || prof->getColorSpace() == icSigCmyData){ + gtk_widget_show(GTK_WIDGET(_box_toomuchink)); + double ink_sum = 0; + for (unsigned int i=0; i<color.icc->colors.size(); i++){ + ink_sum += color.icc->colors[i]; + } + + /* Some literature states that when the sum of paint values exceed 320%, it is considered to be a satured color, + which means the paper can get too wet due to an excessive ammount of ink. This may lead to several issues + such as misalignment and poor quality of printing in general.*/ + if ( ink_sum > 3.2 ) + gtk_widget_set_sensitive (_box_toomuchink, true); + } else { + gtk_widget_hide(GTK_WIDGET(_box_toomuchink)); + } + } +#endif //ENABLE_LCMS + if ( !_updatingrgba ) { gchar s[32]; diff --git a/src/widgets/sp-color-notebook.h b/src/widgets/sp-color-notebook.h index bf6fb1002..5eb29ac73 100644 --- a/src/widgets/sp-color-notebook.h +++ b/src/widgets/sp-color-notebook.h @@ -61,6 +61,9 @@ protected: gulong _entryId; GtkWidget *_book; GtkWidget *_rgbal, *_rgbae; /* RGBA entry */ +#if ENABLE_LCMS + GtkWidget *_box_outofgamut, *_box_colormanaged, *_box_toomuchink; +#endif //ENABLE_LCMS GtkWidget *_p; /* Color preview */ GtkWidget *_btn; GtkWidget *_popup; diff --git a/src/widgets/sp-color-scales.cpp b/src/widgets/sp-color-scales.cpp index cf06247e7..e41b81e5c 100644 --- a/src/widgets/sp-color-scales.cpp +++ b/src/widgets/sp-color-scales.cpp @@ -10,6 +10,8 @@ #include <glibmm/i18n.h> #include "../dialogs/dialog-events.h" #include "sp-color-scales.h" +#include "svg/svg-icc-color.h" +#include "svg/svg-device-color.h" #define CSC_CHANNEL_R (1 << 0) #define CSC_CHANNEL_G (1 << 1) @@ -230,6 +232,12 @@ void ColorScales::_recalcColor( gboolean changing ) case SP_COLOR_SCALES_MODE_CMYK: { _getCmykaFloatv( c ); + color.device = new SVGDeviceColor(); + color.device->type=DEVICE_CMYK; + color.device->colors.clear(); + for (int i=0;i<4;i++){ + color.device->colors.push_back(c[i]); + } float rgb[3]; sp_color_cmyk_to_rgb_floatv( rgb, c[0], c[1], c[2], c[3] ); @@ -241,6 +249,10 @@ void ColorScales::_recalcColor( gboolean changing ) g_warning ("file %s: line %d: Illegal color selector mode %d", __FILE__, __LINE__, _mode); break; } + + /* Preserve ICC */ + color.icc = _color.icc ? new SVGICCColor(*_color.icc) : 0; + _updateInternals( color, alpha, _dragging ); } else @@ -470,11 +482,20 @@ void ColorScales::setMode(SPColorScalesMode mode) gtk_widget_show (_s[4]); gtk_widget_show (_b[4]); _updating = TRUE; - sp_color_rgb_to_cmyk_floatv (c, rgba[0], rgba[1], rgba[2]); - setScaled( _a[0], c[0] ); - setScaled( _a[1], c[1] ); - setScaled( _a[2], c[2] ); - setScaled( _a[3], c[3] ); + + if (_color.device && _color.device->type == DEVICE_CMYK){ + setScaled( _a[0], _color.device->colors[0] ); + setScaled( _a[1], _color.device->colors[1] ); + setScaled( _a[2], _color.device->colors[2] ); + setScaled( _a[3], _color.device->colors[3] ); + } else { + //If we still dont have a device-color, convert from rbga + sp_color_rgb_to_cmyk_floatv (c, rgba[0], rgba[1], rgba[2]); + setScaled( _a[0], c[0] ); + setScaled( _a[1], c[1] ); + setScaled( _a[2], c[2] ); + setScaled( _a[3], c[3] ); + } setScaled( _a[4], rgba[3] ); _updating = FALSE; _updateSliders( CSC_CHANNELS_ALL ); diff --git a/src/widgets/sp-color-wheel-selector.cpp b/src/widgets/sp-color-wheel-selector.cpp index 174b071f9..6012f4e20 100644 --- a/src/widgets/sp-color-wheel-selector.cpp +++ b/src/widgets/sp-color-wheel-selector.cpp @@ -10,7 +10,8 @@ #include "../dialogs/dialog-events.h" #include "sp-color-wheel-selector.h" #include "sp-color-scales.h" - +#include "sp-color-icc-selector.h" +#include "../svg/svg-icc-color.h" G_BEGIN_DECLS @@ -205,6 +206,11 @@ sp_color_wheel_selector_new (void) /* Helpers for setting color value */ +static void preserve_icc(SPColor *color, SPColorWheelSelector *cs){ + ColorSelector* selector = (ColorSelector*)(SP_COLOR_SELECTOR(cs)->base); + color->icc = selector->getColor().icc ? new SVGICCColor(*selector->getColor().icc) : 0; +} + void ColorWheelSelector::_colorChanged() { #ifdef DUMP_CHANGE_INFO @@ -237,6 +243,7 @@ void ColorWheelSelector::_adjustmentChanged( GtkAdjustment *adjustment, SPColorW wheelSelector->_updating = TRUE; + preserve_icc(&wheelSelector->_color, cs); wheelSelector->_updateInternals( wheelSelector->_color, ColorScales::getScaled( wheelSelector->_adj ), wheelSelector->_dragging ); wheelSelector->_updating = FALSE; @@ -249,6 +256,8 @@ void ColorWheelSelector::_sliderGrabbed( SPColorSlider *slider, SPColorWheelSele if (!wheelSelector->_dragging) { wheelSelector->_dragging = TRUE; wheelSelector->_grabbed(); + + preserve_icc(&wheelSelector->_color, cs); wheelSelector->_updateInternals( wheelSelector->_color, ColorScales::getScaled( wheelSelector->_adj ), wheelSelector->_dragging ); } } @@ -260,6 +269,8 @@ void ColorWheelSelector::_sliderReleased( SPColorSlider *slider, SPColorWheelSel if (wheelSelector->_dragging) { wheelSelector->_dragging = FALSE; wheelSelector->_released(); + + preserve_icc(&wheelSelector->_color, cs); wheelSelector->_updateInternals( wheelSelector->_color, ColorScales::getScaled( wheelSelector->_adj ), wheelSelector->_dragging ); } } @@ -269,6 +280,7 @@ void ColorWheelSelector::_sliderChanged( SPColorSlider *slider, SPColorWheelSele (void)slider; ColorWheelSelector* wheelSelector = (ColorWheelSelector*)(SP_COLOR_SELECTOR(cs)->base); + preserve_icc(&wheelSelector->_color, cs); wheelSelector->_updateInternals( wheelSelector->_color, ColorScales::getScaled( wheelSelector->_adj ), wheelSelector->_dragging ); } @@ -285,6 +297,7 @@ void ColorWheelSelector::_wheelChanged( SPColorWheel *wheel, SPColorWheelSelecto sp_color_slider_set_colors (SP_COLOR_SLIDER(wheelSelector->_slider), start, mid, end); + preserve_icc(&color, cs); wheelSelector->_updateInternals( color, wheelSelector->_alpha, sp_color_wheel_is_adjusting( wheel ) ); } diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index ee45665f3..bb13bfdad 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -85,6 +85,7 @@ #include "../svg/css-ostringstream.h" #include "../tools-switch.h" #include "../tweak-context.h" +#include "../spray-context.h" #include "../ui/dialog/calligraphic-profile-rename.h" #include "../ui/icon-names.h" #include "../ui/tool/control-point-selection.h" @@ -99,16 +100,31 @@ #include "../xml/attribute-record.h" #include "../xml/node-event-vector.h" #include "../xml/repr.h" +#include "ui/uxmanager.h" #include "toolbox.h" +#define ENABLE_TASK_SUPPORT 1 + using Inkscape::UnitTracker; +using Inkscape::UI::UXManager; typedef void (*SetupFunction)(GtkWidget *toolbox, SPDesktop *desktop); typedef void (*UpdateFunction)(SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget *toolbox); +enum BarId { + BAR_TOOL = 0, + BAR_AUX, + BAR_COMMANDS, + BAR_SNAP, +}; + +#define BAR_ID_KEY "BarIdValue" +#define HANDLE_POS_MARK "x-inkscape-pos" + static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_tweak_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); +static void sp_spray_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_zoom_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_star_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_arc_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); @@ -128,7 +144,18 @@ static void sp_lpetool_toolbox_prep(SPDesktop *desktop, GtkActionGroup* ma namespace { GtkWidget *sp_text_toolbox_new (SPDesktop *desktop); } -Inkscape::IconSize prefToSize( Glib::ustring const &path, int base ) { +#if ENABLE_TASK_SUPPORT +static void fireTaskChange( EgeSelectOneAction *act, SPDesktop *dt ) +{ + gint selected = ege_select_one_action_get_active( act ); + UXManager::getInstance()->setTask(dt, selected); +} +#endif // ENABLE_TASK_SUPPORT + +using Inkscape::UI::ToolboxFactory; + + +Inkscape::IconSize ToolboxFactory::prefToSize( Glib::ustring const &path, int base ) { static Inkscape::IconSize sizeChoices[] = { Inkscape::ICON_SIZE_LARGE_TOOLBAR, Inkscape::ICON_SIZE_SMALL_TOOLBAR, @@ -148,6 +175,7 @@ static struct { { "SPSelectContext", "select_tool", SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_SELECT_PREFS}, { "InkNodeTool", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS }, { "SPTweakContext", "tweak_tool", SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_TWEAK_PREFS }, + { "SPSprayContext", "spray_tool", SP_VERB_CONTEXT_SPRAY, SP_VERB_CONTEXT_SPRAY_PREFS }, { "SPZoomContext", "zoom_tool", SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS }, { "SPRectContext", "rect_tool", SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_RECT_PREFS }, { "Box3DContext", "3dbox_tool", SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_3DBOX_PREFS }, @@ -183,6 +211,8 @@ static struct { SP_VERB_INVALID, 0, 0}, { "SPTweakContext", "tweak_toolbox", 0, sp_tweak_toolbox_prep, "TweakToolbar", SP_VERB_CONTEXT_TWEAK_PREFS, "/tools/tweak", N_("Color/opacity used for color tweaking")}, + { "SPSprayContext", "spray_toolbox", 0, sp_spray_toolbox_prep, "SprayToolbar", + SP_VERB_CONTEXT_SPRAY_PREFS, "/tools/spray", N_("Color/opacity used for color spraying")}, { "SPZoomContext", "zoom_toolbox", 0, sp_zoom_toolbox_prep, "ZoomToolbar", SP_VERB_INVALID, 0, 0}, { "SPStarContext", "star_toolbox", 0, sp_star_toolbox_prep, "StarToolbar", @@ -301,6 +331,21 @@ static gchar const * ui_descr = " <toolitem action='TweakDoO' />" " </toolbar>" + " <toolbar name='SprayToolbar'>" + " <toolitem action='SprayModeAction' />" + " <separator />" + " <separator />" + " <toolitem action='SprayWidthAction' />" + " <toolitem action='SprayPressureAction' />" + " <toolitem action='SprayPopulationAction' />" + " <separator />" + " <toolitem action='SprayRotationAction' />" + " <toolitem action='SprayScaleAction' />" + " <separator />" + " <toolitem action='SprayStandard_deviationAction' />" + " <toolitem action='SprayMeanAction' />" + " </toolbar>" + " <toolbar name='ZoomToolbar'>" " <toolitem action='ZoomIn' />" " <toolitem action='ZoomOut' />" @@ -452,13 +497,18 @@ static gchar const * ui_descr = " </toolbar>" " <toolbar name='ConnectorToolbar'>" + " <toolitem action='ConnectorEditModeAction' />" " <toolitem action='ConnectorAvoidAction' />" " <toolitem action='ConnectorIgnoreAction' />" + " <toolitem action='ConnectorOrthogonalAction' />" + " <toolitem action='ConnectorCurvatureAction' />" " <toolitem action='ConnectorSpacingAction' />" " <toolitem action='ConnectorGraphAction' />" " <toolitem action='ConnectorLengthAction' />" " <toolitem action='ConnectorDirectedAction' />" " <toolitem action='ConnectorOverlapAction' />" + " <toolitem action='ConnectorNewConnPointAction' />" + " <toolitem action='ConnectorRemoveConnPointAction' />" " </toolbar>" "</ui>" @@ -466,7 +516,7 @@ static gchar const * ui_descr = static Glib::RefPtr<Gtk::ActionGroup> create_or_fetch_actions( SPDesktop* desktop ); -static void toolbox_set_desktop (GtkWidget *toolbox, SPDesktop *desktop, SetupFunction setup_func, UpdateFunction update_func, sigc::connection*); +void setup_snap_toolbox (GtkWidget *toolbox, SPDesktop *desktop); static void setup_tool_toolbox (GtkWidget *toolbox, SPDesktop *desktop); static void update_tool_toolbox (SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget *toolbox); @@ -549,7 +599,7 @@ Gtk::Widget* VerbAction::create_menu_item_vfunc() Gtk::Widget* VerbAction::create_tool_item_vfunc() { // Gtk::Widget* widg = Gtk::Action::create_tool_item_vfunc(); - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/tools/small"); + Inkscape::IconSize toolboxSize = ToolboxFactory::prefToSize("/toolbox/tools/small"); GtkWidget* toolbox = 0; GtkWidget *button = sp_toolbox_button_new_from_verb_with_doubleclick( toolbox, toolboxSize, SP_BUTTON_TYPE_TOGGLE, @@ -757,7 +807,7 @@ Glib::RefPtr<Gtk::ActionGroup> create_or_fetch_actions( SPDesktop* desktop ) SP_VERB_ZOOM_SELECTION, }; - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/small"); + Inkscape::IconSize toolboxSize = ToolboxFactory::prefToSize("/toolbox/small"); static std::map<SPDesktop*, Glib::RefPtr<Gtk::ActionGroup> > groups; Glib::RefPtr<Gtk::ActionGroup> mainActions; @@ -793,6 +843,37 @@ Glib::RefPtr<Gtk::ActionGroup> create_or_fetch_actions( SPDesktop* desktop ) } } +#if ENABLE_TASK_SUPPORT + if ( !mainActions->get_action("TaskSetAction") ) { + GtkListStore* model = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ); + + GtkTreeIter iter; + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Default"), + 1, _("Default interface setup"), + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Custom"), + 1, _("Set the custom task"), + -1 ); + + EgeSelectOneAction* act = ege_select_one_action_new( "TaskSetAction", _("Task"), (""), NULL, GTK_TREE_MODEL(model) ); + g_object_set( act, "short_label", _("Task:"), NULL ); + mainActions->add(Glib::wrap(GTK_ACTION(act))); + //g_object_set_data( holder, "mode_action", act ); + + ege_select_one_action_set_appearance( act, "minimal" ); + ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); + //ege_select_one_action_set_icon_size( act, secondarySize ); + ege_select_one_action_set_tooltip_column( act, 1 ); + + //ege_select_one_action_set_active( act, mode ); + g_signal_connect_after( G_OBJECT(act), "changed", G_CALLBACK(fireTaskChange), desktop ); + } +#endif // ENABLE_TASK_SUPPORT return mainActions; } @@ -810,25 +891,21 @@ void handlebox_attached(GtkHandleBox* /*handlebox*/, GtkWidget* widget, gpointer gtk_widget_set_size_request( widget, -1, -1 ); } - - -GtkWidget * -sp_tool_toolbox_new() +static GtkWidget* toolboxNewCommon( GtkWidget* tb, BarId id, GtkPositionType handlePos ) { - GtkTooltips *tt = gtk_tooltips_new(); - GtkWidget* tb = gtk_toolbar_new(); - gtk_toolbar_set_orientation(GTK_TOOLBAR(tb), GTK_ORIENTATION_VERTICAL); - gtk_toolbar_set_show_arrow(GTK_TOOLBAR(tb), TRUE); - g_object_set_data(G_OBJECT(tb), "desktop", NULL); - g_object_set_data(G_OBJECT(tb), "tooltips", tt); gtk_widget_set_sensitive(tb, FALSE); - GtkWidget *hb = gtk_handle_box_new(); - gtk_handle_box_set_handle_position(GTK_HANDLE_BOX(hb), GTK_POS_TOP); - gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(hb), GTK_SHADOW_OUT); - gtk_handle_box_set_snap_edge(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); + GtkWidget *hb = 0; + if ( UXManager::getInstance()->isFloatWindowProblem() ) { + hb = gtk_event_box_new(); // A simple, neutral container. + } else { + hb = gtk_handle_box_new(); + gtk_handle_box_set_handle_position(GTK_HANDLE_BOX(hb), handlePos); + gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(hb), GTK_SHADOW_OUT); + gtk_handle_box_set_snap_edge(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); + } gtk_container_add(GTK_CONTAINER(hb), tb); gtk_widget_show(GTK_WIDGET(tb)); @@ -836,96 +913,47 @@ sp_tool_toolbox_new() sigc::connection* conn = new sigc::connection; g_object_set_data(G_OBJECT(hb), "event_context_connection", conn); - g_signal_connect(G_OBJECT(hb), "child_detached", G_CALLBACK(handlebox_detached), static_cast<gpointer>(0)); - g_signal_connect(G_OBJECT(hb), "child_attached", G_CALLBACK(handlebox_attached), static_cast<gpointer>(0)); + if ( GTK_IS_HANDLE_BOX(hb) ) { + g_signal_connect(G_OBJECT(hb), "child_detached", G_CALLBACK(handlebox_detached), static_cast<gpointer>(0)); + g_signal_connect(G_OBJECT(hb), "child_attached", G_CALLBACK(handlebox_attached), static_cast<gpointer>(0)); + } + + gpointer val = GINT_TO_POINTER(id); + g_object_set_data(G_OBJECT(hb), BAR_ID_KEY, val); return hb; } -GtkWidget * -sp_aux_toolbox_new() +GtkWidget *ToolboxFactory::createToolToolbox() { GtkWidget *tb = gtk_vbox_new(FALSE, 0); - gtk_box_set_spacing(GTK_BOX(tb), AUX_SPACING); - - g_object_set_data(G_OBJECT(tb), "desktop", NULL); - - gtk_widget_set_sensitive(tb, FALSE); - - GtkWidget *hb = gtk_handle_box_new(); - gtk_handle_box_set_handle_position(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(hb), GTK_SHADOW_OUT); - gtk_handle_box_set_snap_edge(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - - gtk_container_add(GTK_CONTAINER(hb), tb); - gtk_widget_show(GTK_WIDGET(tb)); - - sigc::connection* conn = new sigc::connection; - g_object_set_data(G_OBJECT(hb), "event_context_connection", conn); + return toolboxNewCommon( tb, BAR_TOOL, GTK_POS_TOP ); +} - g_signal_connect(G_OBJECT(hb), "child_detached", G_CALLBACK(handlebox_detached), static_cast<gpointer>(0)); - g_signal_connect(G_OBJECT(hb), "child_attached", G_CALLBACK(handlebox_attached), static_cast<gpointer>(0)); +GtkWidget *ToolboxFactory::createAuxToolbox() +{ + GtkWidget *tb = gtk_vbox_new(FALSE, 0); - return hb; + return toolboxNewCommon( tb, BAR_AUX, GTK_POS_LEFT ); } //#################################### //# Commands Bar //#################################### -GtkWidget * -sp_commands_toolbox_new() +GtkWidget *ToolboxFactory::createCommandsToolbox() { - GtkWidget *tb = gtk_toolbar_new(); - - g_object_set_data(G_OBJECT(tb), "desktop", NULL); - gtk_widget_set_sensitive(tb, FALSE); - - GtkWidget *hb = gtk_handle_box_new(); - gtk_handle_box_set_handle_position(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(hb), GTK_SHADOW_OUT); - gtk_handle_box_set_snap_edge(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - - gtk_container_add(GTK_CONTAINER(hb), tb); - gtk_widget_show(GTK_WIDGET(tb)); - - sigc::connection* conn = new sigc::connection; - g_object_set_data(G_OBJECT(hb), "event_context_connection", conn); - - g_signal_connect(G_OBJECT(hb), "child_detached", G_CALLBACK(handlebox_detached), static_cast<gpointer>(0)); - g_signal_connect(G_OBJECT(hb), "child_attached", G_CALLBACK(handlebox_attached), static_cast<gpointer>(0)); + GtkWidget *tb = gtk_vbox_new(FALSE, 0); - return hb; + return toolboxNewCommon( tb, BAR_COMMANDS, GTK_POS_LEFT ); } -GtkWidget * -sp_snap_toolbox_new() +GtkWidget *ToolboxFactory::createSnapToolbox() { - GtkWidget *tb = gtk_vbox_new(FALSE, 0); - gtk_box_set_spacing(GTK_BOX(tb), AUX_SPACING); - g_object_set_data(G_OBJECT(tb), "desktop", NULL); - - //GtkWidget *tb = gtk_toolbar_new(); - //g_object_set_data(G_OBJECT(tb), "desktop", NULL); - - gtk_widget_set_sensitive(tb, FALSE); - - GtkWidget *hb = gtk_handle_box_new(); - gtk_handle_box_set_handle_position(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - gtk_handle_box_set_shadow_type(GTK_HANDLE_BOX(hb), GTK_SHADOW_OUT); - gtk_handle_box_set_snap_edge(GTK_HANDLE_BOX(hb), GTK_POS_LEFT); - - gtk_container_add(GTK_CONTAINER(hb), tb); - gtk_widget_show(GTK_WIDGET(tb)); - - sigc::connection* conn = new sigc::connection; - g_object_set_data(G_OBJECT(hb), "event_context_connection", conn); - - g_signal_connect(G_OBJECT(hb), "child_detached", G_CALLBACK(handlebox_detached), static_cast<gpointer>(0)); - g_signal_connect(G_OBJECT(hb), "child_attached", G_CALLBACK(handlebox_attached), static_cast<gpointer>(0)); + GtkWidget *tb = gtk_vbox_new(FALSE, 0); - return hb; + return toolboxNewCommon( tb, BAR_SNAP, GTK_POS_LEFT ); } static EgeAdjustmentAction * create_adjustment_action( gchar const *name, @@ -1230,7 +1258,7 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions tracker->setActiveUnit( sp_desktop_namedview(desktop)->doc_units ); g_object_set_data( holder, "tracker", tracker ); - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { InkAction* inky = ink_action_new( "NodeInsertAction", @@ -1256,8 +1284,8 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions { InkAction* inky = ink_action_new( "NodeJoinAction", - _("Join endnodes"), - _("Join selected endnodes"), + _("Join nodes"), + _("Join selected nodes"), INKSCAPE_ICON_NODE_JOIN, secondarySize ); g_object_set( inky, "short_label", _("Join"), NULL ); @@ -1489,55 +1517,41 @@ static void sp_zoom_toolbox_prep(SPDesktop */*desktop*/, GtkActionGroup* /*mainA // no custom GtkAction setup needed } // end of sp_zoom_toolbox_prep() -void -sp_tool_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop) +void ToolboxFactory::setToolboxDesktop(GtkWidget *toolbox, SPDesktop *desktop) { - toolbox_set_desktop(toolbox, - desktop, - setup_tool_toolbox, - update_tool_toolbox, - static_cast<sigc::connection*>(g_object_get_data(G_OBJECT(toolbox), - "event_context_connection"))); -} + sigc::connection *conn = static_cast<sigc::connection*>(g_object_get_data(G_OBJECT(toolbox), + "event_context_connection")); + BarId id = static_cast<BarId>( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toolbox), BAR_ID_KEY)) ); -void -sp_aux_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop) -{ - toolbox_set_desktop(gtk_bin_get_child(GTK_BIN(toolbox)), - desktop, - setup_aux_toolbox, - update_aux_toolbox, - static_cast<sigc::connection*>(g_object_get_data(G_OBJECT(toolbox), - "event_context_connection"))); -} + SetupFunction setup_func = 0; + UpdateFunction update_func = 0; -void -sp_commands_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop) -{ - toolbox_set_desktop(toolbox, - desktop, - setup_commands_toolbox, - update_commands_toolbox, - static_cast<sigc::connection*>(g_object_get_data(G_OBJECT(toolbox), - "event_context_connection"))); -} + switch (id) { + case BAR_TOOL: + setup_func = setup_tool_toolbox; + update_func = update_tool_toolbox; + break; -void -sp_snap_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop) -{ - toolbox_set_desktop(toolbox, - desktop, - setup_snap_toolbox, - update_snap_toolbox, - static_cast<sigc::connection*>(g_object_get_data(G_OBJECT(toolbox), - "event_context_connection"))); -} + case BAR_AUX: + toolbox = gtk_bin_get_child(GTK_BIN(toolbox)); + setup_func = setup_aux_toolbox; + update_func = update_aux_toolbox; + break; + case BAR_COMMANDS: + setup_func = setup_commands_toolbox; + update_func = update_commands_toolbox; + break; + + case BAR_SNAP: + setup_func = setup_snap_toolbox; + update_func = updateSnapToolbox; + break; + default: + g_warning("Unexpected toolbox id encountered."); + } -static void -toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop, SetupFunction setup_func, UpdateFunction update_func, sigc::connection *conn) -{ gpointer ptr = g_object_get_data(G_OBJECT(toolbox), "desktop"); SPDesktop *old_desktop = static_cast<SPDesktop*>(ptr); @@ -1553,18 +1567,124 @@ toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop, SetupFunction setup_ g_object_set_data(G_OBJECT(toolbox), "desktop", (gpointer)desktop); - if (desktop) { + if (desktop && setup_func && update_func) { gtk_widget_set_sensitive(toolbox, TRUE); setup_func(toolbox, desktop); update_func(desktop, desktop->event_context, toolbox); - *conn = desktop->connectEventContextChanged - (sigc::bind (sigc::ptr_fun(update_func), toolbox)); + *conn = desktop->connectEventContextChanged(sigc::bind (sigc::ptr_fun(update_func), toolbox)); } else { gtk_widget_set_sensitive(toolbox, FALSE); } -} // end of toolbox_set_desktop() +} // end of sp_toolbox_set_desktop() + +static void setupToolboxCommon( GtkWidget *toolbox, + SPDesktop *desktop, + gchar const *descr, + gchar const* toolbarName, + gchar const* sizePref ) +{ + Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions( desktop ); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + GtkUIManager* mgr = gtk_ui_manager_new(); + GError* errVal = 0; + + GtkOrientation orientation = GTK_ORIENTATION_HORIZONTAL; + + gtk_ui_manager_insert_action_group( mgr, mainActions->gobj(), 0 ); + gtk_ui_manager_add_ui_from_string( mgr, descr, -1, &errVal ); + + GtkWidget* toolBar = gtk_ui_manager_get_widget( mgr, toolbarName ); + if ( prefs->getBool("/toolbox/icononly", true) ) { + gtk_toolbar_set_style( GTK_TOOLBAR(toolBar), GTK_TOOLBAR_ICONS ); + } + + Inkscape::IconSize toolboxSize = ToolboxFactory::prefToSize(sizePref); + gtk_toolbar_set_icon_size( GTK_TOOLBAR(toolBar), static_cast<GtkIconSize>(toolboxSize) ); + + if (GTK_IS_HANDLE_BOX(toolbox)) { + // g_message("GRABBING ORIENTATION [%s]", toolbarName); + GtkPositionType pos = gtk_handle_box_get_handle_position(GTK_HANDLE_BOX(toolbox)); + orientation = ((pos == GTK_POS_LEFT) || (pos == GTK_POS_RIGHT)) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; + } else { + GtkPositionType pos = static_cast<GtkPositionType>(GPOINTER_TO_INT(g_object_get_data( G_OBJECT(toolbox), HANDLE_POS_MARK ))); + orientation = ((pos == GTK_POS_LEFT) || (pos == GTK_POS_RIGHT)) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; + } + gtk_toolbar_set_orientation(GTK_TOOLBAR(toolBar), orientation); + gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolBar), TRUE); + + g_object_set_data(G_OBJECT(toolBar), "desktop", NULL); + + GtkWidget* child = gtk_bin_get_child(GTK_BIN(toolbox)); + if ( child ) { + gtk_container_remove( GTK_CONTAINER(toolbox), child ); + } + + gtk_container_add( GTK_CONTAINER(toolbox), toolBar ); +} + +void ToolboxFactory::setOrientation(GtkWidget* toolbox, GtkOrientation orientation) +{ + //g_message("Set orientation for %p to be %d", toolbox, orientation); + //GType type = GTK_WIDGET_TYPE(toolbox); + //g_message(" [%s]", g_type_name(type)); + //g_message(" %p", g_object_get_data(G_OBJECT(toolbox), BAR_ID_KEY)); + + GtkPositionType pos = (orientation == GTK_ORIENTATION_HORIZONTAL) ? GTK_POS_LEFT : GTK_POS_TOP; + GtkHandleBox* handleBox = 0; + + if (GTK_IS_BIN(toolbox)) { + //g_message(" is a BIN"); + GtkWidget* child = gtk_bin_get_child(GTK_BIN(toolbox)); + if (child) { + //GType type2 = GTK_WIDGET_TYPE(child); + //g_message(" child [%s]", g_type_name(type2)); + + if (GTK_IS_BOX(child)) { + //g_message(" is a BOX"); + + GList* children = gtk_container_get_children(GTK_CONTAINER(child)); + if (children && children->data) { + //GtkWidget* child2 = GTK_WIDGET(children->data); + //GType type3 = GTK_WIDGET_TYPE(child2); + //g_message(" child [%s]", g_type_name(type3)); + g_message("need to add dynamic switch"); + for (GList* curr = children; curr; curr = g_list_next(curr)) { + GtkWidget* child2 = GTK_WIDGET(curr->data); + + if (GTK_IS_TOOLBAR(child2)) { + GtkToolbar* childBar = GTK_TOOLBAR(child2); + gtk_toolbar_set_orientation(childBar, orientation); + if (GTK_IS_HANDLE_BOX(toolbox)) { + handleBox = GTK_HANDLE_BOX(toolbox); + } + } + } + g_list_free(children); + } else { + // The call is being made before the toolbox proper has been setup. + if (GTK_IS_HANDLE_BOX(toolbox)) { + handleBox = GTK_HANDLE_BOX(toolbox); + } else { + g_object_set_data(G_OBJECT(toolbox), HANDLE_POS_MARK, GINT_TO_POINTER(pos)); + } + } + } else if (GTK_IS_TOOLBAR(child)) { + GtkToolbar* toolbar = GTK_TOOLBAR(child); + gtk_toolbar_set_orientation( toolbar, orientation ); + if (GTK_IS_HANDLE_BOX(toolbox)) { + handleBox = GTK_HANDLE_BOX(toolbox); + } + } + } + } + + if (handleBox) { + gtk_handle_box_set_handle_position(handleBox, pos); + } +} static void setup_tool_toolbox(GtkWidget *toolbox, SPDesktop *desktop) @@ -1575,6 +1695,7 @@ setup_tool_toolbox(GtkWidget *toolbox, SPDesktop *desktop) " <toolitem action='ToolSelector' />" " <toolitem action='ToolNode' />" " <toolitem action='ToolTweak' />" + " <toolitem action='ToolSpray' />" " <toolitem action='ToolZoom' />" " <toolitem action='ToolRect' />" " <toolitem action='Tool3DBox' />" @@ -1593,36 +1714,12 @@ setup_tool_toolbox(GtkWidget *toolbox, SPDesktop *desktop) " <toolitem action='ToolDropper' />" " </toolbar>" "</ui>"; - Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions( desktop ); - GtkUIManager* mgr = gtk_ui_manager_new(); - GError* errVal = 0; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gtk_ui_manager_insert_action_group( mgr, mainActions->gobj(), 0 ); - gtk_ui_manager_add_ui_from_string( mgr, descr, -1, &errVal ); - - GtkWidget* toolBar = gtk_ui_manager_get_widget( mgr, "/ui/ToolToolbar" ); - if ( prefs->getBool("/toolbox/icononly", true) ) { - gtk_toolbar_set_style( GTK_TOOLBAR(toolBar), GTK_TOOLBAR_ICONS ); - } - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/tools/small"); - gtk_toolbar_set_icon_size( GTK_TOOLBAR(toolBar), (GtkIconSize)toolboxSize ); - - gtk_toolbar_set_orientation(GTK_TOOLBAR(toolBar), GTK_ORIENTATION_VERTICAL); - gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolBar), TRUE); - - g_object_set_data(G_OBJECT(toolBar), "desktop", NULL); - - GtkWidget* child = gtk_bin_get_child(GTK_BIN(toolbox)); - if ( child ) { - gtk_container_remove( GTK_CONTAINER(toolbox), child ); - } - - gtk_container_add( GTK_CONTAINER(toolbox), toolBar ); -// Inkscape::IconSize toolboxSize = prefToSize("/toolbox/tools/small"); + setupToolboxCommon( toolbox, desktop, descr, + "/ui/ToolToolbar", + "/toolbox/tools/small"); } - static void update_tool_toolbox( SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget */*toolbox*/ ) { @@ -1668,9 +1765,9 @@ setup_aux_toolbox(GtkWidget *toolbox, SPDesktop *desktop) } else { GtkWidget *sub_toolbox = 0; - if (aux_toolboxes[i].create_func == NULL) + if (aux_toolboxes[i].create_func == NULL) { sub_toolbox = sp_empty_toolbox_new(desktop); - else { + } else { sub_toolbox = aux_toolboxes[i].create_func(desktop); } @@ -1701,7 +1798,7 @@ setup_aux_toolbox(GtkWidget *toolbox, SPDesktop *desktop) gtk_toolbar_set_style( GTK_TOOLBAR(toolBar), GTK_TOOLBAR_ICONS ); } - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/small"); + Inkscape::IconSize toolboxSize = ToolboxFactory::prefToSize("/toolbox/small"); gtk_toolbar_set_icon_size( GTK_TOOLBAR(toolBar), static_cast<GtkIconSize>(toolboxSize) ); gtk_table_attach( GTK_TABLE(holder), toolBar, 0, 1, 0, 1, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0 ); @@ -1785,476 +1882,453 @@ setup_commands_toolbox(GtkWidget *toolbox, SPDesktop *desktop) " <separator />" " <toolitem action='DialogPreferences' />" " <toolitem action='DialogDocumentProperties' />" +#if ENABLE_TASK_SUPPORT + " <separator />" + " <toolitem action='TaskSetAction' />" +#endif // ENABLE_TASK_SUPPORT " </toolbar>" "</ui>"; - Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions( desktop ); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - GtkUIManager* mgr = gtk_ui_manager_new(); - GError* errVal = 0; + setupToolboxCommon( toolbox, desktop, descr, + "/ui/CommandsToolbar", + "/toolbox/small" ); +} - gtk_ui_manager_insert_action_group( mgr, mainActions->gobj(), 0 ); - gtk_ui_manager_add_ui_from_string( mgr, descr, -1, &errVal ); +static void +update_commands_toolbox(SPDesktop */*desktop*/, SPEventContext */*eventcontext*/, GtkWidget */*toolbox*/) +{ +} - GtkWidget* toolBar = gtk_ui_manager_get_widget( mgr, "/ui/CommandsToolbar" ); - if ( prefs->getBool("/toolbox/icononly", true) ) { - gtk_toolbar_set_style( GTK_TOOLBAR(toolBar), GTK_TOOLBAR_ICONS ); +void toggle_snap_callback (GtkToggleAction *act, gpointer data) { //data points to the toolbox + + if (g_object_get_data(G_OBJECT(data), "freeze" )) { + return; } - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/small"); - gtk_toolbar_set_icon_size( GTK_TOOLBAR(toolBar), (GtkIconSize)toolboxSize ); + gpointer ptr = g_object_get_data(G_OBJECT(data), "desktop"); + g_assert(ptr != NULL); - gtk_toolbar_set_orientation(GTK_TOOLBAR(toolBar), GTK_ORIENTATION_HORIZONTAL); - gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolBar), TRUE); + SPDesktop *dt = reinterpret_cast<SPDesktop*>(ptr); + SPNamedView *nv = sp_desktop_namedview(dt); + SPDocument *doc = SP_OBJECT_DOCUMENT(nv); + if (dt == NULL || nv == NULL) { + g_warning("No desktop or namedview specified (in toggle_snap_callback)!"); + return; + } - g_object_set_data(G_OBJECT(toolBar), "desktop", NULL); + Inkscape::XML::Node *repr = SP_OBJECT_REPR(nv); - GtkWidget* child = gtk_bin_get_child(GTK_BIN(toolbox)); - if ( child ) { - gtk_container_remove( GTK_CONTAINER(toolbox), child ); + if (repr == NULL) { + g_warning("This namedview doesn't have a xml representation attached!"); + return; } - gtk_container_add( GTK_CONTAINER(toolbox), toolBar ); -} + bool saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, false); -static void -update_commands_toolbox(SPDesktop */*desktop*/, SPEventContext */*eventcontext*/, GtkWidget */*toolbox*/) -{ -} + bool v = false; + SPAttributeEnum attr = (SPAttributeEnum) GPOINTER_TO_INT(g_object_get_data(G_OBJECT(act), "SP_ATTR_INKSCAPE")); -void toggle_snap_callback (GtkToggleAction *act, gpointer data) { //data points to the toolbox + switch (attr) { + case SP_ATTR_INKSCAPE_SNAP_GLOBAL: + dt->toggleSnapGlobal(); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX: + v = nv->snap_manager.snapprefs.getSnapModeBBox(); + sp_repr_set_boolean(repr, "inkscape:snap-bbox", !v); + break; + case SP_ATTR_INKSCAPE_BBOX_PATHS: + v = nv->snap_manager.snapprefs.getSnapToBBoxPath(); + sp_repr_set_boolean(repr, "inkscape:bbox-paths", !v); + break; + case SP_ATTR_INKSCAPE_BBOX_NODES: + v = nv->snap_manager.snapprefs.getSnapToBBoxNode(); + sp_repr_set_boolean(repr, "inkscape:bbox-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_NODES: + v = nv->snap_manager.snapprefs.getSnapModeNode(); + sp_repr_set_boolean(repr, "inkscape:snap-nodes", !v); + break; + case SP_ATTR_INKSCAPE_OBJECT_PATHS: + v = nv->snap_manager.snapprefs.getSnapToItemPath(); + sp_repr_set_boolean(repr, "inkscape:object-paths", !v); + break; + case SP_ATTR_INKSCAPE_OBJECT_NODES: + v = nv->snap_manager.snapprefs.getSnapToItemNode(); + sp_repr_set_boolean(repr, "inkscape:object-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_SMOOTH_NODES: + v = nv->snap_manager.snapprefs.getSnapSmoothNodes(); + sp_repr_set_boolean(repr, "inkscape:snap-smooth-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS: + v = nv->snap_manager.snapprefs.getSnapIntersectionCS(); + sp_repr_set_boolean(repr, "inkscape:snap-intersection-paths", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_CENTER: + v = nv->snap_manager.snapprefs.getIncludeItemCenter(); + sp_repr_set_boolean(repr, "inkscape:snap-center", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_GRIDS: + v = nv->snap_manager.snapprefs.getSnapToGrids(); + sp_repr_set_boolean(repr, "inkscape:snap-grids", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_TO_GUIDES: + v = nv->snap_manager.snapprefs.getSnapToGuides(); + sp_repr_set_boolean(repr, "inkscape:snap-to-guides", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PAGE: + v = nv->snap_manager.snapprefs.getSnapToPageBorder(); + sp_repr_set_boolean(repr, "inkscape:snap-page", !v); + break; + /*case SP_ATTR_INKSCAPE_SNAP_INTERS_GRIDGUIDE: + v = nv->snap_manager.snapprefs.getSnapIntersectionGG(); + sp_repr_set_boolean(repr, "inkscape:snap-intersection-grid-guide", !v); + break;*/ + case SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS: + v = nv->snap_manager.snapprefs.getSnapLineMidpoints(); + sp_repr_set_boolean(repr, "inkscape:snap-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS: + v = nv->snap_manager.snapprefs.getSnapObjectMidpoints(); + sp_repr_set_boolean(repr, "inkscape:snap-object-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS: + v = nv->snap_manager.snapprefs.getSnapBBoxEdgeMidpoints(); + sp_repr_set_boolean(repr, "inkscape:snap-bbox-edge-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS: + v = nv->snap_manager.snapprefs.getSnapBBoxMidpoints(); + sp_repr_set_boolean(repr, "inkscape:snap-bbox-midpoints", !v); + break; + default: + g_warning("toggle_snap_callback has been called with an ID for which no action has been defined"); + break; + } + + // The snapping preferences are stored in the document, and therefore toggling makes the document dirty + doc->setModifiedSinceSave(); - if (g_object_get_data(G_OBJECT(data), "freeze" )) { - return; - } - - gpointer ptr = g_object_get_data(G_OBJECT(data), "desktop"); - g_assert(ptr != NULL); - - SPDesktop *dt = reinterpret_cast<SPDesktop*>(ptr); - SPNamedView *nv = sp_desktop_namedview(dt); - SPDocument *doc = SP_OBJECT_DOCUMENT(nv); - - if (dt == NULL || nv == NULL) { - g_warning("No desktop or namedview specified (in toggle_snap_callback)!"); - return; - } - - Inkscape::XML::Node *repr = SP_OBJECT_REPR(nv); - - if (repr == NULL) { - g_warning("This namedview doesn't have a xml representation attached!"); - return; - } - - bool saved = sp_document_get_undo_sensitive(doc); - sp_document_set_undo_sensitive(doc, false); - - bool v = false; - SPAttributeEnum attr = (SPAttributeEnum) GPOINTER_TO_INT(g_object_get_data(G_OBJECT(act), "SP_ATTR_INKSCAPE")); - - switch (attr) { - case SP_ATTR_INKSCAPE_SNAP_GLOBAL: - dt->toggleSnapGlobal(); - break; - case SP_ATTR_INKSCAPE_SNAP_BBOX: - v = nv->snap_manager.snapprefs.getSnapModeBBox(); - sp_repr_set_boolean(repr, "inkscape:snap-bbox", !v); - break; - case SP_ATTR_INKSCAPE_BBOX_PATHS: - v = nv->snap_manager.snapprefs.getSnapToBBoxPath(); - sp_repr_set_boolean(repr, "inkscape:bbox-paths", !v); - break; - case SP_ATTR_INKSCAPE_BBOX_NODES: - v = nv->snap_manager.snapprefs.getSnapToBBoxNode(); - sp_repr_set_boolean(repr, "inkscape:bbox-nodes", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_NODES: - v = nv->snap_manager.snapprefs.getSnapModeNode(); - sp_repr_set_boolean(repr, "inkscape:snap-nodes", !v); - break; - case SP_ATTR_INKSCAPE_OBJECT_PATHS: - v = nv->snap_manager.snapprefs.getSnapToItemPath(); - sp_repr_set_boolean(repr, "inkscape:object-paths", !v); - break; - case SP_ATTR_INKSCAPE_OBJECT_NODES: - v = nv->snap_manager.snapprefs.getSnapToItemNode(); - sp_repr_set_boolean(repr, "inkscape:object-nodes", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_SMOOTH_NODES: - v = nv->snap_manager.snapprefs.getSnapSmoothNodes(); - sp_repr_set_boolean(repr, "inkscape:snap-smooth-nodes", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS: - v = nv->snap_manager.snapprefs.getSnapIntersectionCS(); - sp_repr_set_boolean(repr, "inkscape:snap-intersection-paths", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_CENTER: - v = nv->snap_manager.snapprefs.getIncludeItemCenter(); - sp_repr_set_boolean(repr, "inkscape:snap-center", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_GRIDS: - v = nv->snap_manager.snapprefs.getSnapToGrids(); - sp_repr_set_boolean(repr, "inkscape:snap-grids", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_TO_GUIDES: - v = nv->snap_manager.snapprefs.getSnapToGuides(); - sp_repr_set_boolean(repr, "inkscape:snap-to-guides", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_PAGE: - v = nv->snap_manager.snapprefs.getSnapToPageBorder(); - sp_repr_set_boolean(repr, "inkscape:snap-page", !v); - break; - /*case SP_ATTR_INKSCAPE_SNAP_INTERS_GRIDGUIDE: - v = nv->snap_manager.snapprefs.getSnapIntersectionGG(); - sp_repr_set_boolean(repr, "inkscape:snap-intersection-grid-guide", !v); - break;*/ - case SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS: - v = nv->snap_manager.snapprefs.getSnapLineMidpoints(); - sp_repr_set_boolean(repr, "inkscape:snap-midpoints", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS: - v = nv->snap_manager.snapprefs.getSnapObjectMidpoints(); - sp_repr_set_boolean(repr, "inkscape:snap-object-midpoints", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS: - v = nv->snap_manager.snapprefs.getSnapBBoxEdgeMidpoints(); - sp_repr_set_boolean(repr, "inkscape:snap-bbox-edge-midpoints", !v); - break; - case SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS: - v = nv->snap_manager.snapprefs.getSnapBBoxMidpoints(); - sp_repr_set_boolean(repr, "inkscape:snap-bbox-midpoints", !v); - break; - default: - g_warning("toggle_snap_callback has been called with an ID for which no action has been defined"); - break; - } - - // The snapping preferences are stored in the document, and therefore toggling makes the document dirty - doc->setModifiedSinceSave(); - - sp_document_set_undo_sensitive(doc, saved); + sp_document_set_undo_sensitive(doc, saved); } void setup_snap_toolbox(GtkWidget *toolbox, SPDesktop *desktop) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions(desktop); - - gchar const * descr = - "<ui>" - " <toolbar name='SnapToolbar'>" - " <toolitem action='ToggleSnapGlobal' />" - " <separator />" - " <toolitem action='ToggleSnapFromBBoxCorner' />" - " <toolitem action='ToggleSnapToBBoxPath' />" - " <toolitem action='ToggleSnapToBBoxNode' />" - " <toolitem action='ToggleSnapToFromBBoxEdgeMidpoints' />" - " <toolitem action='ToggleSnapToFromBBoxCenters' />" - " <separator />" - " <toolitem action='ToggleSnapFromNode' />" - " <toolitem action='ToggleSnapToItemPath' />" - " <toolitem action='ToggleSnapToPathIntersections' />" - " <toolitem action='ToggleSnapToItemNode' />" - " <toolitem action='ToggleSnapToSmoothNodes' />" - " <toolitem action='ToggleSnapToFromLineMidpoints' />" - " <toolitem action='ToggleSnapToFromObjectCenters' />" - " <toolitem action='ToggleSnapToFromRotationCenter' />" - " <separator />" - " <toolitem action='ToggleSnapToPageBorder' />" - " <toolitem action='ToggleSnapToGrids' />" - " <toolitem action='ToggleSnapToGuides' />" - //" <toolitem action='ToggleSnapToGridGuideIntersections' />" - " </toolbar>" - "</ui>"; - - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapGlobal", - _("Snap"), _("Enable snapping"), INKSCAPE_ICON_SNAP, secondarySize, - SP_ATTR_INKSCAPE_SNAP_GLOBAL); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapFromBBoxCorner", - _("Bounding box"), _("Snap bounding box corners"), INKSCAPE_ICON_SNAP_BOUNDING_BOX, - secondarySize, SP_ATTR_INKSCAPE_SNAP_BBOX); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToBBoxPath", - _("Bounding box edges"), _("Snap to edges of a bounding box"), - INKSCAPE_ICON_SNAP_BOUNDING_BOX_EDGES, secondarySize, SP_ATTR_INKSCAPE_BBOX_PATHS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToBBoxNode", - _("Bounding box corners"), _("Snap to bounding box corners"), - INKSCAPE_ICON_SNAP_BOUNDING_BOX_CORNERS, secondarySize, SP_ATTR_INKSCAPE_BBOX_NODES); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromBBoxEdgeMidpoints", - _("BBox Edge Midpoints"), _("Snap from and to midpoints of bounding box edges"), - INKSCAPE_ICON_SNAP_BOUNDING_BOX_MIDPOINTS, secondarySize, - SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromBBoxCenters", - _("BBox Centers"), _("Snapping from and to centers of bounding boxes"), - INKSCAPE_ICON_SNAP_BOUNDING_BOX_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapFromNode", - _("Nodes"), _("Snap nodes or handles"), INKSCAPE_ICON_SNAP_NODES, secondarySize, SP_ATTR_INKSCAPE_SNAP_NODES); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToItemPath", - _("Paths"), _("Snap to paths"), INKSCAPE_ICON_SNAP_NODES_PATH, secondarySize, - SP_ATTR_INKSCAPE_OBJECT_PATHS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToPathIntersections", - _("Path intersections"), _("Snap to path intersections"), - INKSCAPE_ICON_SNAP_NODES_INTERSECTION, secondarySize, SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToItemNode", - _("To nodes"), _("Snap to cusp nodes"), INKSCAPE_ICON_SNAP_NODES_CUSP, secondarySize, - SP_ATTR_INKSCAPE_OBJECT_NODES); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToSmoothNodes", - _("Smooth nodes"), _("Snap to smooth nodes"), INKSCAPE_ICON_SNAP_NODES_SMOOTH, - secondarySize, SP_ATTR_INKSCAPE_SNAP_SMOOTH_NODES); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromLineMidpoints", - _("Line Midpoints"), _("Snap from and to midpoints of line segments"), - INKSCAPE_ICON_SNAP_NODES_MIDPOINT, secondarySize, SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromObjectCenters", - _("Object Centers"), _("Snap from and to centers of objects"), - INKSCAPE_ICON_SNAP_NODES_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromRotationCenter", - _("Rotation Centers"), _("Snap from and to an item's rotation center"), - INKSCAPE_ICON_SNAP_NODES_ROTATION_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_CENTER); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToPageBorder", - _("Page border"), _("Snap to the page border"), INKSCAPE_ICON_SNAP_PAGE, - secondarySize, SP_ATTR_INKSCAPE_SNAP_PAGE); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGrids", - _("Grids"), _("Snap to grids"), INKSCAPE_ICON_GRID_RECTANGULAR, secondarySize, - SP_ATTR_INKSCAPE_SNAP_GRIDS); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - { - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGuides", - _("Guides"), _("Snap to guides"), INKSCAPE_ICON_GUIDES, secondarySize, - SP_ATTR_INKSCAPE_SNAP_TO_GUIDES); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - } - - /*{ - InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGridGuideIntersections", - _("Grid/guide intersections"), _("Snap to intersections of a grid with a guide"), - INKSCAPE_ICON_SNAP_GRID_GUIDE_INTERSECTIONS, secondarySize, - SP_ATTR_INKSCAPE_SNAP_INTERS_GRIDGUIDE); - - gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); - g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); - }*/ + Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions(desktop); - GtkUIManager* mgr = gtk_ui_manager_new(); - GError* errVal = 0; + gchar const * descr = + "<ui>" + " <toolbar name='SnapToolbar'>" + " <toolitem action='ToggleSnapGlobal' />" + " <separator />" + " <toolitem action='ToggleSnapFromBBoxCorner' />" + " <toolitem action='ToggleSnapToBBoxPath' />" + " <toolitem action='ToggleSnapToBBoxNode' />" + " <toolitem action='ToggleSnapToFromBBoxEdgeMidpoints' />" + " <toolitem action='ToggleSnapToFromBBoxCenters' />" + " <separator />" + " <toolitem action='ToggleSnapFromNode' />" + " <toolitem action='ToggleSnapToItemPath' />" + " <toolitem action='ToggleSnapToPathIntersections' />" + " <toolitem action='ToggleSnapToItemNode' />" + " <toolitem action='ToggleSnapToSmoothNodes' />" + " <toolitem action='ToggleSnapToFromLineMidpoints' />" + " <toolitem action='ToggleSnapToFromObjectCenters' />" + " <toolitem action='ToggleSnapToFromRotationCenter' />" + " <separator />" + " <toolitem action='ToggleSnapToPageBorder' />" + " <toolitem action='ToggleSnapToGrids' />" + " <toolitem action='ToggleSnapToGuides' />" + //" <toolitem action='ToggleSnapToGridGuideIntersections' />" + " </toolbar>" + "</ui>"; - gtk_ui_manager_insert_action_group( mgr, mainActions->gobj(), 0 ); - gtk_ui_manager_add_ui_from_string( mgr, descr, -1, &errVal ); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); - GtkWidget* toolBar = gtk_ui_manager_get_widget( mgr, "/ui/SnapToolbar" ); - if ( prefs->getBool("/toolbox/icononly", true) ) { - gtk_toolbar_set_style( GTK_TOOLBAR(toolBar), GTK_TOOLBAR_ICONS ); + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapGlobal", + _("Snap"), _("Enable snapping"), INKSCAPE_ICON_SNAP, secondarySize, + SP_ATTR_INKSCAPE_SNAP_GLOBAL); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); } - Inkscape::IconSize toolboxSize = prefToSize("/toolbox/secondary"); - gtk_toolbar_set_icon_size( GTK_TOOLBAR(toolBar), static_cast<GtkIconSize>(toolboxSize) ); + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapFromBBoxCorner", + _("Bounding box"), _("Snap bounding box corners"), INKSCAPE_ICON_SNAP_BOUNDING_BOX, + secondarySize, SP_ATTR_INKSCAPE_SNAP_BBOX); - gtk_toolbar_set_orientation(GTK_TOOLBAR(toolBar), GTK_ORIENTATION_HORIZONTAL); - gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolBar), TRUE); + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } - g_object_set_data(G_OBJECT(toolBar), "desktop", NULL); + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToBBoxPath", + _("Bounding box edges"), _("Snap to edges of a bounding box"), + INKSCAPE_ICON_SNAP_BOUNDING_BOX_EDGES, secondarySize, SP_ATTR_INKSCAPE_BBOX_PATHS); - GtkWidget* child = gtk_bin_get_child(GTK_BIN(toolbox)); - if ( child ) { - gtk_container_remove( GTK_CONTAINER(toolbox), child ); + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); } - gtk_container_add( GTK_CONTAINER(toolbox), toolBar ); + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToBBoxNode", + _("Bounding box corners"), _("Snap to bounding box corners"), + INKSCAPE_ICON_SNAP_BOUNDING_BOX_CORNERS, secondarySize, SP_ATTR_INKSCAPE_BBOX_NODES); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromBBoxEdgeMidpoints", + _("BBox Edge Midpoints"), _("Snap from and to midpoints of bounding box edges"), + INKSCAPE_ICON_SNAP_BOUNDING_BOX_MIDPOINTS, secondarySize, + SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINTS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromBBoxCenters", + _("BBox Centers"), _("Snapping from and to centers of bounding boxes"), + INKSCAPE_ICON_SNAP_BOUNDING_BOX_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINTS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapFromNode", + _("Nodes"), _("Snap nodes or handles"), INKSCAPE_ICON_SNAP_NODES, secondarySize, SP_ATTR_INKSCAPE_SNAP_NODES); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToItemPath", + _("Paths"), _("Snap to paths"), INKSCAPE_ICON_SNAP_NODES_PATH, secondarySize, + SP_ATTR_INKSCAPE_OBJECT_PATHS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToPathIntersections", + _("Path intersections"), _("Snap to path intersections"), + INKSCAPE_ICON_SNAP_NODES_INTERSECTION, secondarySize, SP_ATTR_INKSCAPE_SNAP_INTERS_PATHS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToItemNode", + _("To nodes"), _("Snap to cusp nodes"), INKSCAPE_ICON_SNAP_NODES_CUSP, secondarySize, + SP_ATTR_INKSCAPE_OBJECT_NODES); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToSmoothNodes", + _("Smooth nodes"), _("Snap to smooth nodes"), INKSCAPE_ICON_SNAP_NODES_SMOOTH, + secondarySize, SP_ATTR_INKSCAPE_SNAP_SMOOTH_NODES); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromLineMidpoints", + _("Line Midpoints"), _("Snap from and to midpoints of line segments"), + INKSCAPE_ICON_SNAP_NODES_MIDPOINT, secondarySize, SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINTS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromObjectCenters", + _("Object Centers"), _("Snap from and to centers of objects"), + INKSCAPE_ICON_SNAP_NODES_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINTS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToFromRotationCenter", + _("Rotation Centers"), _("Snap from and to an item's rotation center"), + INKSCAPE_ICON_SNAP_NODES_ROTATION_CENTER, secondarySize, SP_ATTR_INKSCAPE_SNAP_CENTER); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToPageBorder", + _("Page border"), _("Snap to the page border"), INKSCAPE_ICON_SNAP_PAGE, + secondarySize, SP_ATTR_INKSCAPE_SNAP_PAGE); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGrids", + _("Grids"), _("Snap to grids"), INKSCAPE_ICON_GRID_RECTANGULAR, secondarySize, + SP_ATTR_INKSCAPE_SNAP_GRIDS); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + { + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGuides", + _("Guides"), _("Snap to guides"), INKSCAPE_ICON_GUIDES, secondarySize, + SP_ATTR_INKSCAPE_SNAP_TO_GUIDES); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + } + + /*{ + InkToggleAction* act = ink_toggle_action_new("ToggleSnapToGridGuideIntersections", + _("Grid/guide intersections"), _("Snap to intersections of a grid with a guide"), + INKSCAPE_ICON_SNAP_GRID_GUIDE_INTERSECTIONS, secondarySize, + SP_ATTR_INKSCAPE_SNAP_INTERS_GRIDGUIDE); + + gtk_action_group_add_action( mainActions->gobj(), GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_snap_callback), toolbox ); + }*/ + + setupToolboxCommon( toolbox, desktop, descr, + "/ui/SnapToolbar", + "/toolbox/secondary" ); +} + +Glib::ustring ToolboxFactory::getToolboxName(GtkWidget* toolbox) +{ + Glib::ustring name; + BarId id = static_cast<BarId>( GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toolbox), BAR_ID_KEY)) ); + switch(id) { + case BAR_TOOL: + name = "ToolToolbar"; + break; + case BAR_AUX: + name = "AuxToolbar"; + break; + case BAR_COMMANDS: + name = "CommandsToolbar"; + break; + case BAR_SNAP: + name = "SnapToolbar"; + break; + } + return name; } -void update_snap_toolbox(SPDesktop *desktop, SPEventContext */*eventcontext*/, GtkWidget *toolbox) -{ - g_assert(desktop != NULL); - g_assert(toolbox != NULL); - - SPNamedView *nv = sp_desktop_namedview(desktop); - if (nv == NULL) { - g_warning("Namedview cannot be retrieved (in update_snap_toolbox)!"); - return; - } - - Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions(desktop); - - Glib::RefPtr<Gtk::Action> act1 = mainActions->get_action("ToggleSnapGlobal"); - Glib::RefPtr<Gtk::Action> act2 = mainActions->get_action("ToggleSnapFromBBoxCorner"); - Glib::RefPtr<Gtk::Action> act3 = mainActions->get_action("ToggleSnapToBBoxPath"); - Glib::RefPtr<Gtk::Action> act4 = mainActions->get_action("ToggleSnapToBBoxNode"); - Glib::RefPtr<Gtk::Action> act4b = mainActions->get_action("ToggleSnapToFromBBoxEdgeMidpoints"); - Glib::RefPtr<Gtk::Action> act4c = mainActions->get_action("ToggleSnapToFromBBoxCenters"); - Glib::RefPtr<Gtk::Action> act5 = mainActions->get_action("ToggleSnapFromNode"); - Glib::RefPtr<Gtk::Action> act6 = mainActions->get_action("ToggleSnapToItemPath"); - Glib::RefPtr<Gtk::Action> act6b = mainActions->get_action("ToggleSnapToPathIntersections"); - Glib::RefPtr<Gtk::Action> act7 = mainActions->get_action("ToggleSnapToItemNode"); - Glib::RefPtr<Gtk::Action> act8 = mainActions->get_action("ToggleSnapToSmoothNodes"); - Glib::RefPtr<Gtk::Action> act9 = mainActions->get_action("ToggleSnapToFromLineMidpoints"); - Glib::RefPtr<Gtk::Action> act10 = mainActions->get_action("ToggleSnapToFromObjectCenters"); - Glib::RefPtr<Gtk::Action> act11 = mainActions->get_action("ToggleSnapToFromRotationCenter"); - Glib::RefPtr<Gtk::Action> act12 = mainActions->get_action("ToggleSnapToPageBorder"); - //Glib::RefPtr<Gtk::Action> act13 = mainActions->get_action("ToggleSnapToGridGuideIntersections"); - Glib::RefPtr<Gtk::Action> act14 = mainActions->get_action("ToggleSnapToGrids"); - Glib::RefPtr<Gtk::Action> act15 = mainActions->get_action("ToggleSnapToGuides"); - - - if (!act1) { - return; // The snap actions haven't been defined yet (might be the case during startup) - } - - // The ..._set_active calls below will toggle the buttons, but this shouldn't lead to - // changes in our document because we're only updating the UI; - // Setting the "freeze" parameter to true will block the code in toggle_snap_callback() - g_object_set_data(G_OBJECT(toolbox), "freeze", GINT_TO_POINTER(TRUE)); - - bool const c1 = nv->snap_manager.snapprefs.getSnapEnabledGlobally(); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act1->gobj()), c1); - - bool const c2 = nv->snap_manager.snapprefs.getSnapModeBBox(); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act2->gobj()), c2); - gtk_action_set_sensitive(GTK_ACTION(act2->gobj()), c1); - - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act3->gobj()), nv->snap_manager.snapprefs.getSnapToBBoxPath()); - gtk_action_set_sensitive(GTK_ACTION(act3->gobj()), c1 && c2); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4->gobj()), nv->snap_manager.snapprefs.getSnapToBBoxNode()); - gtk_action_set_sensitive(GTK_ACTION(act4->gobj()), c1 && c2); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4b->gobj()), nv->snap_manager.snapprefs.getSnapBBoxEdgeMidpoints()); - gtk_action_set_sensitive(GTK_ACTION(act4b->gobj()), c1 && c2); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4c->gobj()), nv->snap_manager.snapprefs.getSnapBBoxMidpoints()); - gtk_action_set_sensitive(GTK_ACTION(act4c->gobj()), c1 && c2); - - bool const c3 = nv->snap_manager.snapprefs.getSnapModeNode(); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act5->gobj()), c3); - gtk_action_set_sensitive(GTK_ACTION(act5->gobj()), c1); - - bool const c4 = nv->snap_manager.snapprefs.getSnapToItemPath(); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act6->gobj()), c4); - gtk_action_set_sensitive(GTK_ACTION(act6->gobj()), c1 && c3); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act6b->gobj()), nv->snap_manager.snapprefs.getSnapIntersectionCS()); - gtk_action_set_sensitive(GTK_ACTION(act6b->gobj()), c1 && c3 && c4); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act7->gobj()), nv->snap_manager.snapprefs.getSnapToItemNode()); - gtk_action_set_sensitive(GTK_ACTION(act7->gobj()), c1 && c3); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act8->gobj()), nv->snap_manager.snapprefs.getSnapSmoothNodes()); - gtk_action_set_sensitive(GTK_ACTION(act8->gobj()), c1 && c3); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act9->gobj()), nv->snap_manager.snapprefs.getSnapLineMidpoints()); - gtk_action_set_sensitive(GTK_ACTION(act9->gobj()), c1 && c3); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act10->gobj()), nv->snap_manager.snapprefs.getSnapObjectMidpoints()); - gtk_action_set_sensitive(GTK_ACTION(act10->gobj()), c1 && c3); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act11->gobj()), nv->snap_manager.snapprefs.getIncludeItemCenter()); - gtk_action_set_sensitive(GTK_ACTION(act11->gobj()), c1 && c3); - - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act12->gobj()), nv->snap_manager.snapprefs.getSnapToPageBorder()); - gtk_action_set_sensitive(GTK_ACTION(act12->gobj()), c1); - //gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act13->gobj()), nv->snap_manager.snapprefs.getSnapIntersectionGG()); - //gtk_action_set_sensitive(GTK_ACTION(act13->gobj()), c1); - - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act14->gobj()), nv->snap_manager.snapprefs.getSnapToGrids()); - gtk_action_set_sensitive(GTK_ACTION(act14->gobj()), c1); - gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act15->gobj()), nv->snap_manager.snapprefs.getSnapToGuides()); - gtk_action_set_sensitive(GTK_ACTION(act15->gobj()), c1); - - - g_object_set_data(G_OBJECT(toolbox), "freeze", GINT_TO_POINTER(FALSE)); // unfreeze (see above) -} - -void show_aux_toolbox(GtkWidget *toolbox_toplevel) +void ToolboxFactory::updateSnapToolbox(SPDesktop *desktop, SPEventContext */*eventcontext*/, GtkWidget *toolbox) +{ + g_assert(desktop != NULL); + g_assert(toolbox != NULL); + + SPNamedView *nv = sp_desktop_namedview(desktop); + if (nv == NULL) { + g_warning("Namedview cannot be retrieved (in updateSnapToolbox)!"); + return; + } + + Glib::RefPtr<Gtk::ActionGroup> mainActions = create_or_fetch_actions(desktop); + + Glib::RefPtr<Gtk::Action> act1 = mainActions->get_action("ToggleSnapGlobal"); + Glib::RefPtr<Gtk::Action> act2 = mainActions->get_action("ToggleSnapFromBBoxCorner"); + Glib::RefPtr<Gtk::Action> act3 = mainActions->get_action("ToggleSnapToBBoxPath"); + Glib::RefPtr<Gtk::Action> act4 = mainActions->get_action("ToggleSnapToBBoxNode"); + Glib::RefPtr<Gtk::Action> act4b = mainActions->get_action("ToggleSnapToFromBBoxEdgeMidpoints"); + Glib::RefPtr<Gtk::Action> act4c = mainActions->get_action("ToggleSnapToFromBBoxCenters"); + Glib::RefPtr<Gtk::Action> act5 = mainActions->get_action("ToggleSnapFromNode"); + Glib::RefPtr<Gtk::Action> act6 = mainActions->get_action("ToggleSnapToItemPath"); + Glib::RefPtr<Gtk::Action> act6b = mainActions->get_action("ToggleSnapToPathIntersections"); + Glib::RefPtr<Gtk::Action> act7 = mainActions->get_action("ToggleSnapToItemNode"); + Glib::RefPtr<Gtk::Action> act8 = mainActions->get_action("ToggleSnapToSmoothNodes"); + Glib::RefPtr<Gtk::Action> act9 = mainActions->get_action("ToggleSnapToFromLineMidpoints"); + Glib::RefPtr<Gtk::Action> act10 = mainActions->get_action("ToggleSnapToFromObjectCenters"); + Glib::RefPtr<Gtk::Action> act11 = mainActions->get_action("ToggleSnapToFromRotationCenter"); + Glib::RefPtr<Gtk::Action> act12 = mainActions->get_action("ToggleSnapToPageBorder"); + //Glib::RefPtr<Gtk::Action> act13 = mainActions->get_action("ToggleSnapToGridGuideIntersections"); + Glib::RefPtr<Gtk::Action> act14 = mainActions->get_action("ToggleSnapToGrids"); + Glib::RefPtr<Gtk::Action> act15 = mainActions->get_action("ToggleSnapToGuides"); + + + if (!act1) { + return; // The snap actions haven't been defined yet (might be the case during startup) + } + + // The ..._set_active calls below will toggle the buttons, but this shouldn't lead to + // changes in our document because we're only updating the UI; + // Setting the "freeze" parameter to true will block the code in toggle_snap_callback() + g_object_set_data(G_OBJECT(toolbox), "freeze", GINT_TO_POINTER(TRUE)); + + bool const c1 = nv->snap_manager.snapprefs.getSnapEnabledGlobally(); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act1->gobj()), c1); + + bool const c2 = nv->snap_manager.snapprefs.getSnapModeBBox(); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act2->gobj()), c2); + gtk_action_set_sensitive(GTK_ACTION(act2->gobj()), c1); + + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act3->gobj()), nv->snap_manager.snapprefs.getSnapToBBoxPath()); + gtk_action_set_sensitive(GTK_ACTION(act3->gobj()), c1 && c2); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4->gobj()), nv->snap_manager.snapprefs.getSnapToBBoxNode()); + gtk_action_set_sensitive(GTK_ACTION(act4->gobj()), c1 && c2); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4b->gobj()), nv->snap_manager.snapprefs.getSnapBBoxEdgeMidpoints()); + gtk_action_set_sensitive(GTK_ACTION(act4b->gobj()), c1 && c2); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act4c->gobj()), nv->snap_manager.snapprefs.getSnapBBoxMidpoints()); + gtk_action_set_sensitive(GTK_ACTION(act4c->gobj()), c1 && c2); + + bool const c3 = nv->snap_manager.snapprefs.getSnapModeNode(); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act5->gobj()), c3); + gtk_action_set_sensitive(GTK_ACTION(act5->gobj()), c1); + + bool const c4 = nv->snap_manager.snapprefs.getSnapToItemPath(); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act6->gobj()), c4); + gtk_action_set_sensitive(GTK_ACTION(act6->gobj()), c1 && c3); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act6b->gobj()), nv->snap_manager.snapprefs.getSnapIntersectionCS()); + gtk_action_set_sensitive(GTK_ACTION(act6b->gobj()), c1 && c3 && c4); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act7->gobj()), nv->snap_manager.snapprefs.getSnapToItemNode()); + gtk_action_set_sensitive(GTK_ACTION(act7->gobj()), c1 && c3); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act8->gobj()), nv->snap_manager.snapprefs.getSnapSmoothNodes()); + gtk_action_set_sensitive(GTK_ACTION(act8->gobj()), c1 && c3); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act9->gobj()), nv->snap_manager.snapprefs.getSnapLineMidpoints()); + gtk_action_set_sensitive(GTK_ACTION(act9->gobj()), c1 && c3); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act10->gobj()), nv->snap_manager.snapprefs.getSnapObjectMidpoints()); + gtk_action_set_sensitive(GTK_ACTION(act10->gobj()), c1 && c3); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act11->gobj()), nv->snap_manager.snapprefs.getIncludeItemCenter()); + gtk_action_set_sensitive(GTK_ACTION(act11->gobj()), c1 && c3); + + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act12->gobj()), nv->snap_manager.snapprefs.getSnapToPageBorder()); + gtk_action_set_sensitive(GTK_ACTION(act12->gobj()), c1); + //gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act13->gobj()), nv->snap_manager.snapprefs.getSnapIntersectionGG()); + //gtk_action_set_sensitive(GTK_ACTION(act13->gobj()), c1); + + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act14->gobj()), nv->snap_manager.snapprefs.getSnapToGrids()); + gtk_action_set_sensitive(GTK_ACTION(act14->gobj()), c1); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act15->gobj()), nv->snap_manager.snapprefs.getSnapToGuides()); + gtk_action_set_sensitive(GTK_ACTION(act15->gobj()), c1); + + + g_object_set_data(G_OBJECT(toolbox), "freeze", GINT_TO_POINTER(FALSE)); // unfreeze (see above) +} + +void ToolboxFactory::showAuxToolbox(GtkWidget *toolbox_toplevel) { gtk_widget_show(toolbox_toplevel); GtkWidget *toolbox = gtk_bin_get_child(GTK_BIN(toolbox_toplevel)); @@ -2268,8 +2342,7 @@ void show_aux_toolbox(GtkWidget *toolbox_toplevel) gtk_widget_show_all(shown_toolbox); } -static GtkWidget * -sp_empty_toolbox_new(SPDesktop *desktop) +static GtkWidget *sp_empty_toolbox_new(SPDesktop *desktop) { GtkWidget *tbl = gtk_toolbar_new(); gtk_object_set_data(GTK_OBJECT(tbl), "dtw", desktop->canvas); @@ -2333,8 +2406,8 @@ static void sp_stb_proportion_value_changed( GtkAdjustment *adj, GObject *dataKl if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { if (!IS_NAN(adj->value)) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setDouble("/tools/shapes/star/proportion", adj->value); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/tools/shapes/star/proportion", adj->value); } } @@ -2650,7 +2723,7 @@ sp_toolbox_add_label(GtkWidget *tbl, gchar const *title, bool wide) static void sp_star_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { EgeOutputAction* act = ege_output_action_new( "StarStateAction", _("<b>New:</b>"), "", 0 ); @@ -3019,7 +3092,7 @@ sp_rect_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl) static void sp_rect_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { EgeAdjustmentAction* eact = 0; - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { EgeOutputAction* act = ege_output_action_new( "RectStateAction", _("<b>New:</b>"), "", 0 ); @@ -3147,7 +3220,7 @@ box3d_set_button_and_adjustment(Persp3D *persp, Proj::Axis axis, // TODO: Take all selected perspectives into account but don't touch the state button if not all of them // have the same state (otherwise a call to box3d_vp_z_state_changed() is triggered and the states // are reset). - bool is_infinite = !persp3d_VP_is_finite(persp, axis); + bool is_infinite = !persp3d_VP_is_finite(persp->perspective_impl, axis); if (is_infinite) { gtk_toggle_action_set_active(tact, TRUE); @@ -3175,6 +3248,10 @@ box3d_resync_toolbar(Inkscape::XML::Node *persp_repr, GObject *data) { GtkAction *act = 0; GtkToggleAction *tact = 0; Persp3D *persp = persp3d_get_from_repr(persp_repr); + if (!persp) { + // Hmm, is it an error if this happens? + return; + } { adj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(tbl), "box3d_angle_x")); act = GTK_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_angle_x_action")); @@ -3261,7 +3338,7 @@ box3d_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl) sp_repr_synthesize_events(persp_repr, &box3d_persp_tb_repr_events, tbl); } - inkscape_active_document()->current_persp3d = persp3d_get_from_repr(persp_repr); + inkscape_active_document()->setCurrentPersp3D(persp3d_get_from_repr(persp_repr)); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setString("/tools/shapes/3dbox/persp", persp_repr->attribute("id")); @@ -3285,7 +3362,6 @@ box3d_angle_value_changed(GtkAdjustment *adj, GObject *dataKludge, Proj::Axis ax // in turn, prevent listener from responding g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(TRUE)); - //Persp3D *persp = document->current_persp3d; std::list<Persp3D *> sel_persps = sp_desktop_selection(desktop)->perspList(); if (sel_persps.empty()) { // this can happen when the document is created; we silently ignore it @@ -3293,7 +3369,7 @@ box3d_angle_value_changed(GtkAdjustment *adj, GObject *dataKludge, Proj::Axis ax } Persp3D *persp = sel_persps.front(); - persp->tmat.set_infinite_direction (axis, adj->value); + persp->perspective_impl->tmat.set_infinite_direction (axis, adj->value); SP_OBJECT(persp)->updateRepr(); // TODO: use the correct axis here, too @@ -3356,7 +3432,7 @@ static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, Inkscape::Preferences *prefs = Inkscape::Preferences::get(); EgeAdjustmentAction* eact = 0; SPDocument *document = sp_desktop_document (desktop); - Persp3D *persp = document->current_persp3d; + Persp3DImpl *persp_impl = document->getCurrentPersp3DImpl(); EgeAdjustmentAction* box3d_angle_x = 0; EgeAdjustmentAction* box3d_angle_y = 0; @@ -3380,7 +3456,7 @@ static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, box3d_angle_x = eact; } - if (!persp3d_VP_is_finite(persp, Proj::X)) { + if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::X)) { gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); } else { gtk_action_set_sensitive( GTK_ACTION(eact), FALSE ); @@ -3420,7 +3496,7 @@ static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, box3d_angle_y = eact; } - if (!persp3d_VP_is_finite(persp, Proj::Y)) { + if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::Y)) { gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); } else { gtk_action_set_sensitive( GTK_ACTION(eact), FALSE ); @@ -3459,7 +3535,7 @@ static void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, box3d_angle_z = eact; } - if (!persp3d_VP_is_finite(persp, Proj::Z)) { + if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::Z)) { gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); } else { gtk_action_set_sensitive( GTK_ACTION(eact), FALSE ); @@ -3658,7 +3734,7 @@ sp_spiral_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl static void sp_spiral_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { EgeAdjustmentAction* eact = 0; - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { EgeOutputAction* act = ege_output_action_new( "SpiralStateAction", _("<b>New:</b>"), "", 0 ); @@ -3765,7 +3841,7 @@ static void sp_add_freehand_mode_toggle(GtkActionGroup* mainActions, GObject* ho { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); guint freehandMode = prefs->getInt(( tool_is_pencil ? "/tools/freehand/pencil/freehand-mode" : "/tools/freehand/pen/freehand-mode" ), 0); - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { GtkListStore* model = gtk_list_store_new( 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING ); @@ -3909,6 +3985,7 @@ sp_pencil_tb_tolerance_value_changed(GtkAdjustment *adj, GObject *tbl) g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); } +/* class PencilToleranceObserver : public Inkscape::Preferences::Observer { public: PencilToleranceObserver(Glib::ustring const &path, GObject *x) : Observer(path), _obj(x) @@ -3917,7 +3994,7 @@ public: } virtual ~PencilToleranceObserver() { if (g_object_get_data(_obj, "prefobserver") == this) { - g_object_set_data(_obj, "prefobserver", NULL); + g_object_set_data(_obj, "prefobserver", NULL); } } virtual void notify(Inkscape::Preferences::Entry const &val) { @@ -3936,7 +4013,7 @@ public: private: GObject *_obj; }; - +*/ static void sp_pencil_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { @@ -3961,9 +4038,6 @@ static void sp_pencil_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActio 1, 2); ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); - - PencilToleranceObserver *obs = - new PencilToleranceObserver("/tools/freehand/pencil/tolerance", G_OBJECT(holder)); } /* advanced shape options */ @@ -4061,7 +4135,7 @@ static void tweak_toggle_doo (GtkToggleAction *act, gpointer /*data*/) { static void sp_tweak_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) { - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); { @@ -4320,6 +4394,228 @@ static void sp_tweak_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainAction //######################## +//## Spray ## +//######################## + +static void sp_spray_width_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/width", adj->value ); +} + +static void sp_spray_mean_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/mean", adj->value ); +} + +static void sp_spray_standard_deviation_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/standard_deviation", adj->value ); +} + +static void sp_spray_pressure_state_changed( GtkToggleAction *act, gpointer /*data*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/spray/usepressure", gtk_toggle_action_get_active(act)); +} + +static void sp_spray_mode_changed( EgeSelectOneAction *act, GObject */*tbl*/ ) +{ + int mode = ege_select_one_action_get_active( act ); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt("/tools/spray/mode", mode); +} + +static void sp_spray_population_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/population", adj->value ); +} + +static void sp_spray_rotation_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/rotation_variation", adj->value ); +} + +static void sp_spray_scale_value_changed( GtkAdjustment *adj, GObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/spray/scale_variation", adj->value ); +} + + +static void sp_spray_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) +{ + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + { + /* Width */ + gchar const* labels[] = {_("(narrow spray)"), 0, 0, 0, _("(default)"), 0, 0, 0, 0, _("(broad spray)")}; + gdouble values[] = {1, 3, 5, 10, 15, 20, 30, 50, 75, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayWidthAction", + _("Width"), _("Width:"), _("The width of the spray area (relative to the visible canvas area)"), + "/tools/spray/width", 15, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-spray", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_width_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + { + /* Mean */ + gchar const* labels[] = {_("(minimum mean)"), 0, 0, _("(default)"), 0, 0, 0, _("(maximum mean)")}; + gdouble values[] = {1, 5, 10, 20, 30, 50, 70, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayMeanAction", + _("Focus"), _("Focus:"), _("0 to spray a spot. Increase to enlarge the ring radius."), + "/tools/spray/mean", 0, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "spray-mean", + 0, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_mean_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + { + /* Standard_deviation */ + gchar const* labels[] = {_("(minimum scatter)"), 0, 0, _("(default)"), 0, 0, 0, _("(maximum scatter)")}; + gdouble values[] = {1, 5, 10, 20, 30, 50, 70, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayStandard_deviationAction", + _("Scatter"), _("Scatter:"), _("Increase to scatter sprayed objects."), + "/tools/spray/standard_deviation", 70, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "spray-standard_deviation", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_standard_deviation_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + /* Mode */ + { + GtkListStore* model = gtk_list_store_new( 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING ); + + GtkTreeIter iter; + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Spray with copies"), + 1, _("Spray copies of the initial selection"), + 2, INKSCAPE_ICON_SPRAY_COPY_MODE, + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Spray with clones"), + 1, _("Spray clones of the initial selection"), + 2, INKSCAPE_ICON_SPRAY_CLONE_MODE, + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Spray single path"), + 1, _("Spray objects in a single path"), + 2, INKSCAPE_ICON_SPRAY_UNION_MODE, + -1 ); + + EgeSelectOneAction* act = ege_select_one_action_new( "SprayModeAction", _("Mode"), (""), NULL, GTK_TREE_MODEL(model) ); + g_object_set( act, "short_label", _("Mode:"), NULL ); + gtk_action_group_add_action( mainActions, GTK_ACTION(act) ); + g_object_set_data( holder, "mode_action", act ); + + ege_select_one_action_set_appearance( act, "full" ); + ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); + g_object_set( G_OBJECT(act), "icon-property", "iconId", NULL ); + ege_select_one_action_set_icon_column( act, 2 ); + ege_select_one_action_set_icon_size( act, secondarySize ); + ege_select_one_action_set_tooltip_column( act, 1 ); + + gint mode = prefs->getInt("/tools/spray/mode", 1); + ege_select_one_action_set_active( act, mode ); + g_signal_connect_after( G_OBJECT(act), "changed", G_CALLBACK(sp_spray_mode_changed), holder ); + + g_object_set_data( G_OBJECT(holder), "spray_tool_mode", act); + } + + { /* Population */ + gchar const* labels[] = {_("(low population)"), 0, 0, _("(default)"), 0, 0, _("(high population)")}; + gdouble values[] = {10, 25, 35, 50, 60, 80, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayPopulationAction", + _("Amount"), _("Amount:"), + _("Adjusts the number of items sprayed per clic."), + "/tools/spray/population", 70, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "spray-population", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_population_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + g_object_set_data( holder, "spray_population", eact ); + } + + /* Use Pressure button */ + { + InkToggleAction* act = ink_toggle_action_new( "SprayPressureAction", + _("Pressure"), + _("Use the pressure of the input device to alter the amount of sprayed objects."), + "use_pressure", + Inkscape::ICON_SIZE_DECORATION ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_spray_pressure_state_changed), NULL); + gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/spray/usepressure", true) ); + } + + { /* Rotation */ + gchar const* labels[] = {_("(low rotation variation)"), 0, 0, _("(default)"), 0, 0, _("(high rotation variation)")}; + gdouble values[] = {10, 25, 35, 50, 60, 80, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayRotationAction", + _("Rotation"), _("Rotation:"), + // xgettext:no-c-format + _("Variation of the rotation of the sprayed objects. 0% for the same rotation than the original object."), + "/tools/spray/rotation_variation", 0, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "spray-rotation", + 0, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_rotation_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + g_object_set_data( holder, "spray_rotation", eact ); + } + + { /* Scale */ + gchar const* labels[] = {_("(low scale variation)"), 0, 0, _("(default)"), 0, 0, _("(high scale variation)")}; + gdouble values[] = {10, 25, 35, 50, 60, 80, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "SprayScaleAction", + _("Scale"), _("Scale:"), + // xgettext:no-c-format + _("Variation in the scale of the sprayed objects. 0% for the same scale than the original object."), + "/tools/spray/scale_variation", 0, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "spray-scale", + 0, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_spray_scale_value_changed, 1, 0 ); + ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + g_object_set_data( holder, "spray_scale", eact ); + } + + + +} + + +//######################## //## Calligraphy ## //######################## static void update_presets_list (GObject *tbl) @@ -4366,9 +4662,9 @@ static void update_presets_list (GObject *tbl) } } } - } + } - if (match) { + if (match) { // newly added item is at the same index as the // save command, so we need to change twice for it to take effect ege_select_one_action_set_active(sel, 0); @@ -4497,7 +4793,7 @@ static void sp_dcc_build_presets_list(GObject *tbl) int ii=1; for (std::vector<Glib::ustring>::iterator i = presets.begin(); i != presets.end(); ++i) { - GtkTreeIter iter; + GtkTreeIter iter; Glib::ustring preset_name = prefs->getString(*i + "/name"); gtk_list_store_append( model, &iter ); gtk_list_store_set( model, &iter, 0, _(preset_name.data()), 1, ii++, -1 ); @@ -4549,12 +4845,12 @@ static void sp_dcc_save_profile (GtkWidget */*widget*/, GObject *tbl) int temp_index = 0; for (std::vector<Glib::ustring>::iterator i = presets.begin(); i != presets.end(); ++i, ++temp_index) { - Glib::ustring name = prefs->getString(*i + "/name"); - if (!name.empty() && profile_name == name) { - new_index = temp_index; + Glib::ustring name = prefs->getString(*i + "/name"); + if (!name.empty() && profile_name == name) { + new_index = temp_index; save_path = *i; break; - } + } } if (new_index == -1) { @@ -4666,7 +4962,7 @@ static void sp_calligraphy_toolbox_prep(SPDesktop *desktop, GtkActionGroup* main GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-calligraphy", 1, 100, 1.0, 10.0, labels, values, G_N_ELEMENTS(labels), - sp_ddc_width_value_changed, 1, 0); + sp_ddc_width_value_changed, 1, 0 ); ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); @@ -5100,7 +5396,7 @@ static void sp_arc_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, Inkscape::Preferences *prefs = Inkscape::Preferences::get(); EgeAdjustmentAction* eact = 0; - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); { @@ -5792,9 +6088,9 @@ sp_text_toolbox_selection_changed (Inkscape::Selection */*selection*/, GObject * if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING || result_numbers == QUERY_STYLE_NOTHING) { // there are no texts in selection, read from prefs - sp_style_read_from_prefs(query, "/tools/text"); + sp_style_read_from_prefs(query, "/tools/text"); - if (g_object_get_data(tbl, "text_style_from_prefs")) { + if (g_object_get_data(tbl, "text_style_from_prefs")) { // do not reset the toolbar style from prefs if we already did it last time sp_style_unref(query); g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); @@ -5945,7 +6241,7 @@ sp_text_toolbox_selection_modified (Inkscape::Selection *selection, guint /*flag } void -sp_text_toolbox_subselection_changed (gpointer /*dragger*/, GObject *tbl) +sp_text_toolbox_subselection_changed (gpointer /*tc*/, GObject *tbl) { sp_text_toolbox_selection_changed (NULL, tbl); } @@ -6071,8 +6367,85 @@ sp_text_toolbox_anchoring_toggled (GtkRadioButton *button, int prop = GPOINTER_TO_INT(data); SPDesktop *desktop = SP_ACTIVE_DESKTOP; - SPCSSAttr *css = sp_repr_css_attr_new (); + // move the x of all texts to preserve the same bbox + Inkscape::Selection *selection = sp_desktop_selection(desktop); + for (GSList const *items = selection->itemList(); items != NULL; items = items->next) { + if (SP_IS_TEXT((SPItem *) items->data)) { + SPItem *item = SP_ITEM(items->data); + + unsigned writing_mode = SP_OBJECT_STYLE(item)->writing_mode.value; + // below, variable names suggest horizontal move, but we check the writing direction + // and move in the corresponding axis + int axis; + if (writing_mode == SP_CSS_WRITING_MODE_LR_TB || writing_mode == SP_CSS_WRITING_MODE_RL_TB) { + axis = NR::X; + } else { + axis = NR::Y; + } + + Geom::OptRect bbox + = item->getBounds(Geom::identity(), SPItem::GEOMETRIC_BBOX); + if (!bbox) + continue; + double width = bbox->dimensions()[axis]; + // If you want to align within some frame, other than the text's own bbox, calculate + // the left and right (or top and bottom for tb text) slacks of the text inside that + // frame (currently unused) + double left_slack = 0; + double right_slack = 0; + unsigned old_align = SP_OBJECT_STYLE(item)->text_align.value; + double move = 0; + if (old_align == SP_CSS_TEXT_ALIGN_START || old_align == SP_CSS_TEXT_ALIGN_LEFT) { + switch (prop) { + case 0: + move = -left_slack; + break; + case 1: + move = width/2 + (right_slack - left_slack)/2; + break; + case 2: + move = width + right_slack; + break; + } + } else if (old_align == SP_CSS_TEXT_ALIGN_CENTER) { + switch (prop) { + case 0: + move = -width/2 - left_slack; + break; + case 1: + move = (right_slack - left_slack)/2; + break; + case 2: + move = width/2 + right_slack; + break; + } + } else if (old_align == SP_CSS_TEXT_ALIGN_END || old_align == SP_CSS_TEXT_ALIGN_RIGHT) { + switch (prop) { + case 0: + move = -width - left_slack; + break; + case 1: + move = -width/2 + (right_slack - left_slack)/2; + break; + case 2: + move = right_slack; + break; + } + } + Geom::Point XY = SP_TEXT(item)->attributes.firstXY(); + if (axis == NR::X) { + XY = XY + Geom::Point (move, 0); + } else { + XY = XY + Geom::Point (0, move); + } + SP_TEXT(item)->attributes.setFirstXY(XY); + SP_OBJECT(item)->updateRepr(); + SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + + SPCSSAttr *css = sp_repr_css_attr_new (); switch (prop) { case 0: @@ -6111,8 +6484,8 @@ sp_text_toolbox_anchoring_toggled (GtkRadioButton *button, // If querying returned nothing, read the style from the text tool prefs (default style for new texts) if (result_numbers == QUERY_STYLE_NOTHING) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->mergeStyle("/tools/text/style", css); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); @@ -6215,8 +6588,8 @@ sp_text_toolbox_style_toggled (GtkToggleButton *button, // If querying returned nothing, read the style from the text tool prefs (default style for new texts) if (result_fontspec == QUERY_STYLE_NOTHING) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->mergeStyle("/tools/text/style", css); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); @@ -6265,7 +6638,7 @@ sp_text_toolbox_orientation_toggled (GtkRadioButton *button, if (result_numbers == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->mergeStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_desktop_set_style (desktop, css, true, true); @@ -6300,7 +6673,7 @@ sp_text_toolbox_family_keypress (GtkWidget */*w*/, GdkEventKey *event, GObject * } gboolean -sp_text_toolbox_family_list_keypress (GtkWidget *w, GdkEventKey *event, GObject */*tbl*/) +sp_text_toolbox_family_list_keypress (GtkWidget */*w*/, GdkEventKey *event, GObject */*tbl*/) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!desktop) return FALSE; @@ -6377,7 +6750,7 @@ sp_text_toolbox_size_changed (GtkComboBox *cbox, if (result_numbers == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->mergeStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); @@ -6467,27 +6840,35 @@ cell_data_func (GtkCellLayout */*cell_layout*/, gtk_tree_model_get(tree_model, iter, 0, &family, -1); gchar *const family_escaped = g_markup_escape_text(family, -1); - static char const *const sample = _("AaBbCcIiPpQq12369$\342\202\254\302\242?.;/()"); - gchar *const sample_escaped = g_markup_escape_text(sample, -1); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int show_sample = prefs->getInt("/tools/text/show_sample_in_list", 1); + if (show_sample) { + + Glib::ustring sample = prefs->getString("/tools/text/font_sample"); + gchar *const sample_escaped = g_markup_escape_text(sample.data(), -1); std::stringstream markup; markup << family_escaped << " <span foreground='darkgray' font_family='" << family_escaped << "'>" << sample_escaped << "</span>"; g_object_set (G_OBJECT (cell), "markup", markup.str().c_str(), NULL); + g_free(sample_escaped); + } else { + g_object_set (G_OBJECT (cell), "markup", family_escaped, NULL); + } + g_free(family); g_free(family_escaped); - g_free(sample_escaped); } -gboolean text_toolbox_completion_match_selected (GtkEntryCompletion *widget, - GtkTreeModel *model, - GtkTreeIter *iter, - GObject *tbl) +gboolean text_toolbox_completion_match_selected(GtkEntryCompletion */*widget*/, + GtkTreeModel *model, + GtkTreeIter *iter, + GObject *tbl) { // We intercept this signal so as to fire family_changed at once (without it, you'd have to // press Enter again after choosing a completion) - gchar *family; + gchar *family = 0; gtk_tree_model_get(model, iter, 0, &family, -1); GtkEntry *entry = GTK_ENTRY (g_object_get_data (G_OBJECT (tbl), "family-entry")); @@ -6520,9 +6901,9 @@ cbe_add_completion (GtkComboBoxEntry *cbe, GObject *tbl){ g_object_unref(completion); } -void sp_text_toolbox_family_popnotify (GtkComboBox *widget, - void *property, - GObject *tbl) +void sp_text_toolbox_family_popnotify(GtkComboBox *widget, + void */*property*/, + GObject *tbl) { // while the drop-down is open, we disable font family changing, reenabling it only when it closes @@ -6573,11 +6954,10 @@ void sp_text_toolbox_family_popnotify (GtkComboBox *widget, } } -GtkWidget* -sp_text_toolbox_new (SPDesktop *desktop) +GtkWidget *sp_text_toolbox_new (SPDesktop *desktop) { GtkToolbar *tbl = GTK_TOOLBAR(gtk_toolbar_new()); - GtkIconSize secondarySize = static_cast<GtkIconSize>(prefToSize("/toolbox/secondary", 1)); + GtkIconSize secondarySize = static_cast<GtkIconSize>(ToolboxFactory::prefToSize("/toolbox/secondary", 1)); gtk_object_set_data(GTK_OBJECT(tbl), "dtw", desktop->canvas); gtk_object_set_data(GTK_OBJECT(tbl), "desktop", desktop); @@ -6607,7 +6987,7 @@ sp_text_toolbox_new (SPDesktop *desktop) // expand the field a bit so as to view more of the previews in the drop-down GtkRequisition req; gtk_widget_size_request (GTK_WIDGET (font_sel->gobj()), &req); - gtk_widget_set_size_request (GTK_WIDGET (font_sel->gobj()), req.width + 40, -1); + gtk_widget_set_size_request (GTK_WIDGET (font_sel->gobj()), MIN(req.width + 50, 500), -1); GtkWidget* entry = (GtkWidget*) font_sel->get_entry()->gobj(); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (sp_text_toolbox_family_changed), tbl); @@ -6802,6 +7182,13 @@ sp_text_toolbox_new (SPDesktop *desktop) //## Connector ## //######################### +static void sp_connector_mode_toggled( GtkToggleAction* act, GtkObject */*tbl*/ ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/connector/mode", + gtk_toggle_action_get_active( act )); +} + static void sp_connector_path_set_avoid(void) { cc_selection_set_avoid(true); @@ -6813,6 +7200,106 @@ static void sp_connector_path_set_ignore(void) cc_selection_set_avoid(false); } +static void sp_connector_orthogonal_toggled( GtkToggleAction* act, GObject *tbl ) +{ + SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" ); + Inkscape::Selection * selection = sp_desktop_selection(desktop); + SPDocument *doc = sp_desktop_document(desktop); + + if (!sp_document_get_undo_sensitive(doc)) + { + return; + } + + + // quit if run by the _changed callbacks + if (g_object_get_data( tbl, "freeze" )) { + return; + } + + // in turn, prevent callbacks from responding + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE) ); + + bool is_orthog = gtk_toggle_action_get_active( act ); + gchar orthog_str[] = "orthogonal"; + gchar polyline_str[] = "polyline"; + gchar *value = is_orthog ? orthog_str : polyline_str ; + + bool modmade = false; + GSList *l = (GSList *) selection->itemList(); + while (l) { + SPItem *item = (SPItem *) l->data; + + if (cc_item_is_connector(item)) { + sp_object_setAttribute(item, "inkscape:connector-type", + value, false); + item->avoidRef->handleSettingChange(); + modmade = true; + } + l = l->next; + } + + if (!modmade) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/connector/orthogonal", is_orthog); + } + + sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, + is_orthog ? _("Set connector type: orthogonal"): _("Set connector type: polyline")); + + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); +} + +static void connector_curvature_changed(GtkAdjustment *adj, GObject* tbl) +{ + SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" ); + Inkscape::Selection * selection = sp_desktop_selection(desktop); + SPDocument *doc = sp_desktop_document(desktop); + + if (!sp_document_get_undo_sensitive(doc)) + { + return; + } + + + // quit if run by the _changed callbacks + if (g_object_get_data( tbl, "freeze" )) { + return; + } + + // in turn, prevent callbacks from responding + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE) ); + + gdouble newValue = gtk_adjustment_get_value(adj); + gchar value[G_ASCII_DTOSTR_BUF_SIZE]; + g_ascii_dtostr(value, G_ASCII_DTOSTR_BUF_SIZE, newValue); + + bool modmade = false; + GSList *l = (GSList *) selection->itemList(); + while (l) { + SPItem *item = (SPItem *) l->data; + + if (cc_item_is_connector(item)) { + sp_object_setAttribute(item, "inkscape:connector-curvature", + value, false); + item->avoidRef->handleSettingChange(); + modmade = true; + } + l = l->next; + } + + if (!modmade) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(Glib::ustring("/tools/connector/curvature"), newValue); + } + + sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, + _("Change connector curvature")); + + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); +} static void connector_spacing_changed(GtkAdjustment *adj, GObject* tbl) @@ -6894,7 +7381,7 @@ static void sp_nooverlaps_graph_layout_toggled( GtkToggleAction* act, GtkObject } -static void connector_length_changed(GtkAdjustment *adj, GObject* tbl) +static void connector_length_changed(GtkAdjustment *adj, GObject* /*tbl*/) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setDouble("/tools/connector/length", adj->value); @@ -6909,21 +7396,37 @@ static void connector_tb_event_attr_changed(Inkscape::XML::Node *repr, if (g_object_get_data(G_OBJECT(tbl), "freeze")) { return; } - if (strcmp(name, "inkscape:connector-spacing") != 0) { - return; - } + if (strcmp(name, "inkscape:connector-spacing") == 0) + { + GtkAdjustment *adj = (GtkAdjustment*) + gtk_object_get_data(GTK_OBJECT(tbl), "spacing"); + gdouble spacing = defaultConnSpacing; + sp_repr_get_double(repr, "inkscape:connector-spacing", &spacing); - GtkAdjustment *adj = (GtkAdjustment*) - gtk_object_get_data(GTK_OBJECT(tbl), "spacing"); - gdouble spacing = defaultConnSpacing; - sp_repr_get_double(repr, "inkscape:connector-spacing", &spacing); + gtk_adjustment_set_value(adj, spacing); + gtk_adjustment_value_changed(adj); + } - gtk_adjustment_set_value(adj, spacing); - gtk_adjustment_value_changed(adj); - spinbutton_defocus(GTK_OBJECT(tbl)); } +static void sp_connector_new_connection_point(GtkWidget *, GObject *tbl) +{ + SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" ); + SPConnectorContext* cc = SP_CONNECTOR_CONTEXT(desktop->event_context); + + if (cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE) + cc_create_connection_point(cc); +} + +static void sp_connector_remove_connection_point(GtkWidget *, GObject *tbl) +{ + SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" ); + SPConnectorContext* cc = SP_CONNECTOR_CONTEXT(desktop->event_context); + + if (cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE) + cc_remove_connection_point(cc); +} static Inkscape::XML::NodeEventVector connector_tb_repr_events = { NULL, /* child_added */ @@ -6933,11 +7436,41 @@ static Inkscape::XML::NodeEventVector connector_tb_repr_events = { NULL /* order_changed */ }; +static void sp_connector_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl) +{ + GtkAdjustment *adj = GTK_ADJUSTMENT( g_object_get_data( tbl, "curvature" ) ); + GtkToggleAction *act = GTK_TOGGLE_ACTION( g_object_get_data( tbl, "orthogonal" ) ); + SPItem *item = selection->singleItem(); + if (SP_IS_PATH(item)) + { + gdouble curvature = SP_PATH(item)->connEndPair.getCurvature(); + bool is_orthog = SP_PATH(item)->connEndPair.isOrthogonal(); + gtk_toggle_action_set_active(act, is_orthog); + gtk_adjustment_set_value(adj, curvature); + } + +} static void sp_connector_toolbox_prep( SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder ) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Inkscape::IconSize secondarySize = prefToSize("/toolbox/secondary", 1); + Inkscape::IconSize secondarySize = ToolboxFactory::prefToSize("/toolbox/secondary", 1); + + // Editing mode toggle button + { + InkToggleAction* act = ink_toggle_action_new( "ConnectorEditModeAction", + _("EditMode"), + _("Switch between connection point editing and connector drawing mode"), + INKSCAPE_ICON_CONNECTOR_EDIT, + Inkscape::ICON_SIZE_DECORATION ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + + bool tbuttonstate = prefs->getBool("/tools/connector/mode"); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), ( tbuttonstate ? TRUE : FALSE )); + g_object_set_data( holder, "mode", act ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_connector_mode_toggled), holder ); + } + { InkAction* inky = ink_action_new( "ConnectorAvoidAction", @@ -6959,17 +7492,42 @@ static void sp_connector_toolbox_prep( SPDesktop *desktop, GtkActionGroup* mainA gtk_action_group_add_action( mainActions, GTK_ACTION(inky) ); } + // Orthogonal connectors toggle button + { + InkToggleAction* act = ink_toggle_action_new( "ConnectorOrthogonalAction", + _("Orthogonal"), + _("Make connector orthogonal or polyline"), + INKSCAPE_ICON_CONNECTOR_ORTHOGONAL, + Inkscape::ICON_SIZE_DECORATION ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + + bool tbuttonstate = prefs->getBool("/tools/connector/orthogonal"); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), ( tbuttonstate ? TRUE : FALSE )); + g_object_set_data( holder, "orthogonal", act ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_connector_orthogonal_toggled), holder ); + } + EgeAdjustmentAction* eact = 0; + // Curvature spinbox + eact = create_adjustment_action( "ConnectorCurvatureAction", + _("Connector Curvature"), _("Curvature:"), + _("The amount of connectors curvature"), + "/tools/connector/curvature", defaultConnCurvature, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "inkscape:connector-curvature", + 0, 100, 1.0, 10.0, + 0, 0, 0, + connector_curvature_changed, 1, 0 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); // Spacing spinbox eact = create_adjustment_action( "ConnectorSpacingAction", - _("Connector Spacing"), _("Spacing:"), - _("The amount of space left around objects by auto-routing connectors"), - "/tools/connector/spacing", defaultConnSpacing, - GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "inkscape:connector-spacing", - 0, 100, 1.0, 10.0, - 0, 0, 0, - connector_spacing_changed, 1, 0 ); + _("Connector Spacing"), _("Spacing:"), + _("The amount of space left around objects by auto-routing connectors"), + "/tools/connector/spacing", defaultConnSpacing, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "inkscape:connector-spacing", + 0, 100, 1.0, 10.0, + 0, 0, 0, + connector_spacing_changed, 1, 0 ); gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); // Graph (connector network) layout @@ -7008,6 +7566,8 @@ static void sp_connector_toolbox_prep( SPDesktop *desktop, GtkActionGroup* mainA gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), ( tbuttonstate ? TRUE : FALSE )); g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_directed_graph_layout_toggled), holder ); + sigc::connection *connection = new sigc::connection(sp_desktop_selection(desktop)->connectChanged(sigc::bind(sigc::ptr_fun(sp_connector_toolbox_selection_changed), (GObject *)holder)) + ); } // Avoid overlaps toggle button @@ -7025,6 +7585,31 @@ static void sp_connector_toolbox_prep( SPDesktop *desktop, GtkActionGroup* mainA g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_nooverlaps_graph_layout_toggled), holder ); } + + // New connection point button + { + InkAction* inky = ink_action_new( "ConnectorNewConnPointAction", + _("New connection point"), + _("Add a new connection point to the currently selected item"), + INKSCAPE_ICON_CONNECTOR_NEW_CONNPOINT, + secondarySize ); + g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_connector_new_connection_point), holder ); + gtk_action_group_add_action( mainActions, GTK_ACTION(inky) ); + } + + // Remove selected connection point button + + { + InkAction* inky = ink_action_new( "ConnectorRemoveConnPointAction", + _("Remove connection point"), + _("Remove the currently selected connection point"), + INKSCAPE_ICON_CONNECTOR_REMOVE_CONNPOINT, + secondarySize ); + g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_connector_remove_connection_point), holder ); + gtk_action_group_add_action( mainActions, GTK_ACTION(inky) ); + } + + // Code to watch for changes to the connector-spacing attribute in // the XML. Inkscape::XML::Node *repr = SP_OBJECT_REPR(desktop->namedview); diff --git a/src/widgets/toolbox.h b/src/widgets/toolbox.h index 4bc417e8f..2e4b2958a 100644 --- a/src/widgets/toolbox.h +++ b/src/widgets/toolbox.h @@ -7,6 +7,7 @@ * Authors: * Lauris Kaplinski <lauris@kaplinski.com> * Frank Felfe <innerspace@iname.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 1999-2002 Authors * Copyright (C) 2001-2002 Ximian, Inc. @@ -16,38 +17,45 @@ #include <gtk/gtkstyle.h> #include <gtk/gtktooltips.h> +#include <glibmm/ustring.h> #include "forward.h" #include "icon-size.h" -GtkWidget *sp_tool_toolbox_new (); -void sp_tool_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop); +namespace Inkscape { +namespace UI { -GtkWidget *sp_aux_toolbox_new (); -void sp_aux_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop); +class ToolboxFactory +{ +public: + static void setToolboxDesktop(GtkWidget *toolbox, SPDesktop *desktop); + static void setOrientation(GtkWidget* toolbox, GtkOrientation orientation); + static void showAuxToolbox(GtkWidget* toolbox); -GtkWidget *sp_commands_toolbox_new (); -void sp_commands_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop); + static GtkWidget *createToolToolbox(); + static GtkWidget *createAuxToolbox(); + static GtkWidget *createCommandsToolbox(); + static GtkWidget *createSnapToolbox(); -GtkWidget *sp_snap_toolbox_new (); -void sp_snap_toolbox_set_desktop(GtkWidget *toolbox, SPDesktop *desktop); -void update_snap_toolbox(SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget *toolbox); -void setup_snap_toolbox (GtkWidget *toolbox, SPDesktop *desktop); + static Glib::ustring getToolboxName(GtkWidget* toolbox); -void show_aux_toolbox(GtkWidget *toolbox); + static void updateSnapToolbox(SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget *toolbox); -GtkWidget *sp_toolbox_button_normal_new_from_verb(GtkWidget *t, - Inkscape::IconSize size, - Inkscape::Verb * verb, - Inkscape::UI::View::View *view, - GtkTooltips *tt); + static Inkscape::IconSize prefToSize(Glib::ustring const &path, int base = 0 ); + +private: + ToolboxFactory(); +}; + +} // namespace UI +} // namespace Inkscape -void aux_toolbox_space(GtkWidget *tb, gint space); // utility + +// TODO remove this: void sp_toolbox_add_label(GtkWidget *tbl, gchar const *title, bool wide = true); -Inkscape::IconSize prefToSize(Glib::ustring const &path, int base = 0 ); #endif /* !SEEN_TOOLBOX_H */ |
