diff options
| author | Ted Gould <ted@gould.cx> | 2009-11-29 19:01:07 +0000 |
|---|---|---|
| committer | Ted Gould <ted@gould.cx> | 2009-11-29 19:01:07 +0000 |
| commit | 29d3c0b15028e61f176df3a75189bf0959d0d03e (patch) | |
| tree | 727afe596c693a9bdd098d72618abd9ceb0d1969 /src | |
| parent | Add the build dir dbus directory to grab some headerfiles for distcheck. (diff) | |
| parent | hopefully fix build on linux (diff) | |
| download | inkscape-29d3c0b15028e61f176df3a75189bf0959d0d03e.tar.gz inkscape-29d3c0b15028e61f176df3a75189bf0959d0d03e.zip | |
Merging in from trunk
(bzr r8254.1.37)
Diffstat (limited to 'src')
192 files changed, 3896 insertions, 2612 deletions
diff --git a/src/2geom/bezier.h b/src/2geom/bezier.h index 4ab965f42..9e68d93ae 100644 --- a/src/2geom/bezier.h +++ b/src/2geom/bezier.h @@ -46,35 +46,32 @@ namespace Geom { inline Coord subdivideArr(Coord t, Coord const *v, Coord *left, Coord *right, unsigned order) { /* * Bernstein : - * Evaluate a Bernstein function at a particular parameter value + * Evaluate a Bernstein function at a particular parameter value * Fill in control points for resulting sub-curves. * */ unsigned N = order+1; - std::valarray<Coord> vtemp(2*N); + std::valarray<Coord> row(N); for (unsigned i = 0; i < N; i++) - vtemp[i] = v[i]; + row[i] = v[i]; // Triangle computation const double omt = (1-t); if(left) - left[0] = vtemp[0]; + left[0] = row[0]; if(right) - right[order] = vtemp[order]; - double *prev_row = &vtemp[0]; - double *row = &vtemp[N]; + right[order] = row[order]; for (unsigned i = 1; i < N; i++) { for (unsigned j = 0; j < N - i; j++) { - row[j] = omt*prev_row[j] + t*prev_row[j+1]; + row[j] = omt*row[j] + t*row[j+1]; } if(left) left[i] = row[0]; if(right) right[order-i] = row[order-i]; - std::swap(prev_row, row); } - return (prev_row[0]); + return (row[0]); /* Coord vtemp[order+1][order+1]; @@ -97,6 +94,20 @@ inline Coord subdivideArr(Coord t, Coord const *v, Coord *left, Coord *right, un return (vtemp[order][0]);*/ } +template <typename T> +inline T bernsteinValueAt(double t, T const *c_, unsigned n) { + double u = 1.0 - t; + double bc = 1; + double tn = 1; + T tmp = c_[0]*u; + for(unsigned i=1; i<n; i++){ + tn = tn*t; + bc = bc*(n-i+1)/i; + tmp = (tmp + tn*bc*c_[i])*u; + } + return (tmp + tn*t*c_[n]); +} + class Bezier { private: @@ -225,24 +236,34 @@ public: //inline Coord const &operator[](unsigned ix) const { return c_[ix]; } inline void setPoint(unsigned ix, double val) { c_[ix] = val; } - /* This is inelegant, as it uses several extra stores. I think there might be a way to - * evaluate roughly in situ. */ - + /** + * The size of the returned vector equals n_derivs+1. + */ std::vector<Coord> valueAndDerivatives(Coord t, unsigned n_derivs) const { - std::vector<Coord> val_n_der; + /* This is inelegant, as it uses several extra stores. I think there might be a way to + * evaluate roughly in situ. */ + + // initialize return vector with zeroes, such that we only need to replace the non-zero derivs + std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0)); + + // initialize temp storage variables std::valarray<Coord> d_(order()+1); - unsigned nn = n_derivs + 1; // the size of the result vector equals n_derivs+1 ... - if(nn > order()) - nn = order()+1; // .. but with a maximum of order() + 1! - for(unsigned i = 0; i < size(); i++) + for (unsigned i = 0; i < size(); i++) { d_[i] = c_[i]; - for(unsigned di = 0; di < nn; di++) { - val_n_der.push_back(subdivideArr(t, &d_[0], NULL, NULL, order() - di)); - for(unsigned i = 0; i < order() - di; i++) { + } + + unsigned nn = n_derivs + 1; + if(n_derivs > order()) { + nn = order()+1; // only calculate the non zero derivs + } + for (unsigned di = 0; di < nn; di++) { + //val_n_der[di] = (subdivideArr(t, &d_[0], NULL, NULL, order() - di)); + val_n_der[di] = bernsteinValueAt(t, &d_[0], order() - di); + for (unsigned i = 0; i < order() - di; i++) { d_[i] = (order()-di)*(d_[i+1] - d_[i]); } } - val_n_der.resize(n_derivs); + return val_n_der; } @@ -257,6 +278,11 @@ public: find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, 0.0, 1.0); return solutions; } + std::vector<double> roots(Interval const ivl) const { + std::vector<double> solutions; + find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, ivl[0], ivl[1]); + return solutions; + } }; diff --git a/src/2geom/d2-sbasis.cpp b/src/2geom/d2-sbasis.cpp index 2c52e4782..aef989fc7 100644 --- a/src/2geom/d2-sbasis.cpp +++ b/src/2geom/d2-sbasis.cpp @@ -58,8 +58,19 @@ Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &M){ return result; } -Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, - Piecewise<D2<SBasis> > const &b){ +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f[ + * f(t) \rightarrow \left\{ + * \begin{array}{c} + * a_1 \bullet b_1 \\ + * a_2 \bullet b_2 \\ + * \ldots \\ + * a_n \bullet b_n \\ + * \end{array}\right. + * \f] + * @relates Piecewise */ +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b) +{ Piecewise<SBasis > result; if (a.empty() || b.empty()) return result; Piecewise<D2<SBasis> > aa = partition(a,b.cuts); @@ -72,6 +83,30 @@ Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, return result; } +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f[ + * f(t) \rightarrow \left\{ + * \begin{array}{c} + * a_1 \bullet b \\ + * a_2 \bullet b \\ + * \ldots \\ + * a_n \bullet b \\ + * \end{array}\right. + * \f] + * @relates Piecewise */ +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b) +{ + Piecewise<SBasis > result; + if (a.empty()) return result; + + result.push_cut(a.cuts.front()); + for (unsigned i = 0; i < a.size(); ++i){ + result.push(dot(a.segs[i],b), a.cuts[i+1]); + } + return result; +} + + Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b){ Piecewise<SBasis > result; diff --git a/src/2geom/d2-sbasis.h b/src/2geom/d2-sbasis.h index c61052da7..d404e0618 100644 --- a/src/2geom/d2-sbasis.h +++ b/src/2geom/d2-sbasis.h @@ -73,6 +73,7 @@ Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a); D2<Piecewise<SBasis> > make_cuts_independent(Piecewise<D2<SBasis> > const &a); Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &a); Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b); +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b); Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b); Piecewise<D2<SBasis> > operator*(Piecewise<D2<SBasis> > const &a, Matrix const &m); diff --git a/src/2geom/d2.h b/src/2geom/d2.h index afa00b40d..b2a0f8866 100644 --- a/src/2geom/d2.h +++ b/src/2geom/d2.h @@ -97,10 +97,10 @@ class D2{ } std::vector<Point > valueAndDerivatives(double t, unsigned n) const { std::vector<Coord> x = f[X].valueAndDerivatives(t, n), - y = f[Y].valueAndDerivatives(t, n); - std::vector<Point> res; + y = f[Y].valueAndDerivatives(t, n); // always returns a vector of size n+1 + std::vector<Point> res(n+1); for(unsigned i = 0; i <= n; i++) { - res.push_back(Point(x[i], y[i])); + res[i] = Point(x[i], y[i]); } return res; } @@ -321,6 +321,25 @@ dot(D2<T> const & a, D2<T> const & b) { return r; } +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f$a \bullet b = a_X b_X + a_Y b_Y\f$. + * @relates D2 */ +template <typename T> +inline T +dot(D2<T> const & a, Point const & b) { + boost::function_requires<AddableConcept<T> >(); + boost::function_requires<ScalableConcept<T> >(); + + T r; + for(unsigned i = 0; i < 2; i++) { + r += a[i] * b[i]; + } + return r; +} + +/** @brief Calculates the 'cross product' or 'outer product' of \c a and \c b + * @return \f$a \times b = a_Y b_X - a_X b_Y\f$. + * @relates D2 */ template <typename T> inline T cross(D2<T> const & a, D2<T> const & b) { diff --git a/src/2geom/matrix.cpp b/src/2geom/matrix.cpp index 04a21d624..cc91743b1 100644 --- a/src/2geom/matrix.cpp +++ b/src/2geom/matrix.cpp @@ -179,7 +179,8 @@ Matrix Matrix::inverse() const { Matrix d; Geom::Coord const determ = det(); - if (!are_near(determ, 0.0)) { + // the numerical precision of the determinant must be significant + if (fabs(determ) > 1e-18) { Geom::Coord const ideterm = 1.0 / determ; d._c[0] = _c[3] * ideterm; diff --git a/src/2geom/piecewise.h b/src/2geom/piecewise.h index a5be42587..a0628daf1 100644 --- a/src/2geom/piecewise.h +++ b/src/2geom/piecewise.h @@ -58,7 +58,7 @@ namespace Geom { * \begin{array}{cc} * s_1,& t <= c_2 \\ * s_2,& c_2 <= t <= c_3\\ - * \ldots + * \ldots \\ * s_n,& c_n <= t * \end{array}\right. * \f] @@ -105,6 +105,10 @@ class Piecewise { inline output_type lastValue() const { return valueAt(cuts.back()); } + + /** + * The size of the returned vector equals n_derivs+1. + */ std::vector<output_type> valueAndDerivatives(double t, unsigned n_derivs) const { unsigned n = segN(t); std::vector<output_type> ret, val = segs[n].valueAndDerivatives(segT(t, n), n_derivs); @@ -115,6 +119,7 @@ class Piecewise { } return ret; } + //TODO: maybe it is not a good idea to have this? Piecewise<T> operator()(SBasis f); Piecewise<T> operator()(Piecewise<SBasis>f); @@ -773,10 +778,10 @@ Piecewise<T> reverse(Piecewise<T> const &f) { return ret; } - /** * Interpolates between a and b. * \return a if t = 0, b if t = 1, or an interpolation between a and b for t in [0,1] + * \relates Piecewise */ template<typename T> Piecewise<T> lerp(double t, Piecewise<T> const &a, Piecewise<T> b) { diff --git a/src/2geom/svg-path.h b/src/2geom/svg-path.h index f2902750c..f1fd67867 100644 --- a/src/2geom/svg-path.h +++ b/src/2geom/svg-path.h @@ -64,33 +64,58 @@ public: void moveTo(Point p) { finish(); _path.start(p); + _start_p = p; _in_path = true; } //TODO: what if _in_path = false? void hlineTo(Coord v) { - _path.template appendNew<HLineSegment>(Point(v, _path.finalPoint()[Y])); + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<HLineSegment>(Point(v, _path.finalPoint()[Y])); } void vlineTo(Coord v) { - _path.template appendNew<VLineSegment>(Point(_path.finalPoint()[X], v)); + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<VLineSegment>(Point(_path.finalPoint()[X], v)); } void lineTo(Point p) { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } _path.template appendNew<LineSegment>(p); } void quadTo(Point c, Point p) { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } _path.template appendNew<QuadraticBezier>(c, p); } void curveTo(Point c0, Point c1, Point p) { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } _path.template appendNew<CubicBezier>(c0, c1, p); } void arcTo(double rx, double ry, double angle, bool large_arc, bool sweep, Point p) { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } _path.template appendNew<SVGEllipticalArc>(rx, ry, angle, large_arc, sweep, p); } @@ -113,6 +138,7 @@ protected: bool _in_path; OutputIterator _out; Path _path; + Point _start_p; }; typedef std::back_insert_iterator<std::vector<Path> > iter; diff --git a/src/Makefile.am b/src/Makefile.am index e1fe86535..8f9682832 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,9 +16,9 @@ bin_PROGRAMS = inkscape inkview # Libraries which should be compiled by "make" but not installed. # Use this only for libraries that are really standalone, rather than for # source tree subdirectories. -if WITH_INKBOARD -libpedro = pedro/libpedro.a -endif +#if WITH_INKBOARD +#libpedro = pedro/libpedro.a +#endif noinst_LIBRARIES = \ libinkscape.a \ dom/libdom.a \ @@ -115,8 +115,8 @@ include filters/Makefile_insert include helper/Makefile_insert include inkjar/Makefile_insert include io/Makefile_insert -include pedro/Makefile_insert -include jabber_whiteboard/Makefile_insert +#include pedro/Makefile_insert +#include jabber_whiteboard/Makefile_insert include libcroco/Makefile_insert include libgdl/Makefile_insert include libnr/Makefile_insert @@ -199,6 +199,7 @@ EXTRA_DIST = \ traits/function.h \ traits/list-copy.h \ traits/reference.h \ + show-preview.bmp \ $(jabber_whiteboard_SOURCES) \ $(CXXTEST_TEMPLATE) @@ -269,10 +270,11 @@ 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 # including the the testsuites here ensures that they get distributed -cxxtests_SOURCES = cxxtests.cpp $(CXXTEST_TESTSUITES) -cxxtests_LDADD = libnr/nr-compose-reference.o $(all_libs) +cxxtests_SOURCES = cxxtests.cpp libnr/nr-compose-reference.cpp $(CXXTEST_TESTSUITES) +cxxtests_LDADD = $(all_libs) cxxtests.cpp: $(CXXTEST_TESTSUITES) $(CXXTEST_TEMPLATE) $(CXXTESTGEN) -o cxxtests.cpp $(CXXTEST_TESTSUITES) diff --git a/src/arc-context.cpp b/src/arc-context.cpp index d47e22b1a..e689c93db 100644 --- a/src/arc-context.cpp +++ b/src/arc-context.cpp @@ -50,6 +50,7 @@ static void sp_arc_context_init(SPArcContext *arc_context); static void sp_arc_context_dispose(GObject *object); static void sp_arc_context_setup(SPEventContext *ec); +static void sp_arc_context_finish(SPEventContext *ec); static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event); static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); @@ -89,6 +90,7 @@ static void sp_arc_context_class_init(SPArcContextClass *klass) object_class->dispose = sp_arc_context_dispose; event_context_class->setup = sp_arc_context_setup; + event_context_class->finish = sp_arc_context_finish; event_context_class->root_handler = sp_arc_context_root_handler; event_context_class->item_handler = sp_arc_context_item_handler; } @@ -111,6 +113,20 @@ static void sp_arc_context_init(SPArcContext *arc_context) new (&arc_context->sel_changed_connection) sigc::connection(); } +static void sp_arc_context_finish(SPEventContext *ec) +{ + SPArcContext *ac = SP_ARC_CONTEXT(ec); + SPDesktop *desktop = ec->desktop; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + sp_arc_finish(ac); + ac->sel_changed_connection.disconnect(); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } +} + static void sp_arc_context_dispose(GObject *object) { SPEventContext *ec = SP_EVENT_CONTEXT(object); @@ -223,7 +239,6 @@ static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent if (event->button.button == 1 && !event_context->space_panning) { dragging = true; - sp_event_context_snap_window_open(event_context); ac->center = Inkscape::setup_for_drag_start(desktop, event_context, event); /* Snap center */ @@ -267,7 +282,7 @@ static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent event_context->xp = event_context->yp = 0; if (event->button.button == 1 && !event_context->space_panning) { dragging = false; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the arc sp_arc_finish(ac); @@ -324,7 +339,7 @@ static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent case GDK_Escape: if (dragging) { dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); // if drawing, cancel, otherwise pass it up for deselecting sp_arc_cancel(ac); ret = TRUE; @@ -335,7 +350,7 @@ static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the arc sp_arc_finish(ac); diff --git a/src/attributes-test.h b/src/attributes-test.h index d6851a42b..1d8f32843 100644 --- a/src/attributes-test.h +++ b/src/attributes-test.h @@ -44,6 +44,7 @@ struct {char const *attr; bool supported;} const all_attrs[] = { {"begin", true}, {"additive", true}, {"font", true}, + {"-inkscape-font-specification", true}, // TODO look into this attribute's name {"marker", true}, {"line-height", true}, @@ -338,7 +339,7 @@ struct {char const *attr; bool supported;} const all_attrs[] = { {"sodipodi:insensitive", true}, {"sodipodi:nonprintable", true}, {"inkscape:groupmode", true}, - {"sodipodi:version", true}, + {"sodipodi:version", false}, {"inkscape:version", true}, {"inkscape:object-paths", true}, {"inkscape:object-nodes", true}, @@ -355,9 +356,9 @@ struct {char const *attr; bool supported;} const all_attrs[] = { {"inkscape:snap-object-midpoints", true}, {"inkscape:snap-bbox-edge-midpoints", true}, {"inkscape:snap-bbox-midpoints", true}, - //{"inkscape:snap-intersection-grid-guide", true}, - {"inkscape:snap-grids", true}, - {"inkscape:snap-to-guides", true}, + //{"inkscape:snap-intersection-grid-guide", true}, + {"inkscape:snap-grids", true}, + {"inkscape:snap-to-guides", true}, {"inkscape:snap-intersection-paths", true}, {"inkscape:original-d", true}, {"inkscape:pageopacity", true}, diff --git a/src/bind/Makefile_insert b/src/bind/Makefile_insert index e91ff47f5..b640957d3 100644 --- a/src/bind/Makefile_insert +++ b/src/bind/Makefile_insert @@ -6,4 +6,6 @@ ink_common_sources += \ bind/javabind.cpp \ bind/dobinding.cpp \ bind/javainc/jni.h \ - bind/javainc/linux/jni_md.h + bind/javainc/linux/jni_md.h \ + bind/javainc/solaris/jni_md.h \ + bind/javainc/win32/jni_md.h diff --git a/src/box3d-context.cpp b/src/box3d-context.cpp index d1b6d62fc..e3476deb3 100644 --- a/src/box3d-context.cpp +++ b/src/box3d-context.cpp @@ -54,6 +54,7 @@ static void sp_box3d_context_init(Box3DContext *box3d_context); static void sp_box3d_context_dispose(GObject *object); static void sp_box3d_context_setup(SPEventContext *ec); +static void sp_box3d_context_finish(SPEventContext *ec); static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEvent *event); static gint sp_box3d_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); @@ -92,6 +93,7 @@ static void sp_box3d_context_class_init(Box3DContextClass *klass) object_class->dispose = sp_box3d_context_dispose; event_context_class->setup = sp_box3d_context_setup; + event_context_class->finish = sp_box3d_context_finish; event_context_class->root_handler = sp_box3d_context_root_handler; event_context_class->item_handler = sp_box3d_context_item_handler; } @@ -119,6 +121,21 @@ static void sp_box3d_context_init(Box3DContext *box3d_context) new (&box3d_context->sel_changed_connection) sigc::connection(); } +static void sp_box3d_context_finish(SPEventContext *ec) +{ + 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); + bc->sel_changed_connection.disconnect(); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } +} + + static void sp_box3d_context_dispose(GObject *object) { Box3DContext *bc = SP_BOX3D_CONTEXT(object); @@ -276,7 +293,6 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, event->button.state & GDK_CONTROL_MASK); dragging = true; - sp_event_context_snap_window_open(event_context); /* */ Geom::Point button_dt(desktop->w2d(button_w)); @@ -373,7 +389,7 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven event_context->xp = event_context->yp = 0; if ( event->button.button == 1 && !event_context->space_panning) { dragging = false; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the box @@ -508,7 +524,7 @@ static gint sp_box3d_context_root_handler(SPEventContext *event_context, GdkEven sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the box sp_box3d_finish(bc); @@ -609,10 +625,14 @@ static void sp_box3d_drag(Box3DContext &bc, guint /*state*/) static void sp_box3d_finish(Box3DContext *bc) { bc->_message_context->clear(); - g_assert (SP_ACTIVE_DOCUMENT->current_persp3d); + bc->ctrl_dragged = false; + bc->extruded = false; if ( bc->item != NULL ) { SPDesktop * desktop = SP_EVENT_CONTEXT_DESKTOP(bc); + SPDocument *doc = sp_desktop_document(desktop); + if (!doc || !doc->current_persp3d) + return; SPBox3D *box = SP_BOX3D(bc->item); @@ -631,9 +651,6 @@ static void sp_box3d_finish(Box3DContext *bc) bc->item = NULL; } - - bc->ctrl_dragged = false; - bc->extruded = false; } void sp_box3d_context_update_lines(SPEventContext *ec) { diff --git a/src/box3d-side.cpp b/src/box3d-side.cpp index 241279100..69bae53d9 100644 --- a/src/box3d-side.cpp +++ b/src/box3d-side.cpp @@ -225,6 +225,16 @@ box3d_side_set_shape (SPShape *shape) box3d_side_compute_corner_ids(side, corners); SPCurve *c = new SPCurve(); + + if (!box3d_get_corner_screen(box, corners[0]).isFinite() || + !box3d_get_corner_screen(box, corners[1]).isFinite() || + !box3d_get_corner_screen(box, corners[2]).isFinite() || + !box3d_get_corner_screen(box, corners[3]).isFinite() ) + { + g_warning ("Trying to draw a 3D box side with invalid coordinates.\n"); + return; + } + c->moveto(box3d_get_corner_screen(box, corners[0])); c->lineto(box3d_get_corner_screen(box, corners[1])); c->lineto(box3d_get_corner_screen(box, corners[2])); diff --git a/src/box3d.cpp b/src/box3d.cpp index 5cffa66d9..93efa5c35 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -335,7 +335,8 @@ box3d_set_transform(SPItem *item, Geom::Matrix const &xform) Persp3D *persp = box3d_get_perspective(box); Persp3D *transf_persp; - if (!persp3d_has_all_boxes_in_selection (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 */ diff --git a/src/color-profile.cpp b/src/color-profile.cpp index 5868a9582..4b1307316 100644 --- a/src/color-profile.cpp +++ b/src/color-profile.cpp @@ -1,4 +1,6 @@ - +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif //#define DEBUG_LCMS @@ -12,10 +14,14 @@ #include <cstring> #include <string> -// #ifdef WIN32 -// #include <windows.h> -// #include <Icm.h> -// #endif + +#ifdef WIN32 +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. Required for correctly including icm.h +#define _WIN32_WINDOWS 0x0410 +#endif +#include <windows.h> +#endif + #include "xml/repr.h" #include "color-profile.h" #include "color-profile-fns.h" @@ -27,6 +33,9 @@ #include "dom/uri.h" #include "dom/util/digest.h" +#ifdef WIN32 +#include <Icm.h> +#endif // WIN32 using Inkscape::ColorProfile; using Inkscape::ColorProfileClass; @@ -263,14 +272,17 @@ void ColorProfile::set( SPObject *object, unsigned key, gchar const *value ) // Normal for files that have not yet been saved. docbase = ""; } + + gchar* escaped = g_uri_escape_string(cprof->href, "!*'();:@=+$,/?#[]", TRUE); + //g_message("docbase:%s\n", docbase); org::w3c::dom::URI docUri(docbase); //# 2. Get href of icc file. we don't care if it's rel or abs - org::w3c::dom::URI hrefUri(cprof->href); + org::w3c::dom::URI hrefUri(escaped); //# 3. Resolve the href according the docBase. This follows // the w3c specs. All absolute and relative issues are considered org::w3c::dom::URI cprofUri = docUri.resolve(hrefUri); - gchar* fullname = g_strdup((gchar *)cprofUri.getNativePath().c_str()); + gchar* fullname = g_uri_unescape_string(cprofUri.getNativePath().c_str(), ""); cprof->_clearProfile(); cprof->profHandle = cmsOpenProfileFromFile( fullname, "r" ); if ( cprof->profHandle ) { @@ -280,6 +292,8 @@ void ColorProfile::set( SPObject *object, unsigned key, gchar const *value ) #ifdef DEBUG_LCMS DEBUG_MESSAGE( lcmsOne, "cmsOpenProfileFromFile( '%s'...) = %p", fullname, (void*)cprof->profHandle ); #endif // DEBUG_LCMS + g_free(escaped); + escaped = 0; g_free(fullname); #endif // ENABLE_LCMS } @@ -478,7 +492,7 @@ cmsHPROFILE Inkscape::colorprofile_get_handle( SPDocument* document, guint* inte cmsHTRANSFORM ColorProfile::getTransfToSRGB8() { - if ( !_transf ) { + if ( !_transf && profHandle ) { int intent = getLcmsIntent(rendering_intent); _transf = cmsCreateTransform( profHandle, _getInputFormat(_profileSpace), getSRGBProfile(), TYPE_RGBA_8, intent, 0 ); } @@ -487,7 +501,7 @@ cmsHTRANSFORM ColorProfile::getTransfToSRGB8() cmsHTRANSFORM ColorProfile::getTransfFromSRGB8() { - if ( !_revTransf ) { + if ( !_revTransf && profHandle ) { int intent = getLcmsIntent(rendering_intent); _revTransf = cmsCreateTransform( getSRGBProfile(), TYPE_RGBA_8, profHandle, _getInputFormat(_profileSpace), intent, 0 ); } @@ -604,21 +618,21 @@ std::list<Glib::ustring> ColorProfile::getProfileDirs() { } -// #ifdef WIN32 -// wchar_t pathBuf[MAX_PATH + 1]; -// pathBuf[0] = 0; -// DWORD pathSize = sizeof(pathBuf); -// g_assert(sizeof(wchar_t) == sizeof(gunichar2)); -// if ( GetColorDirectoryW( NULL, &pathBuf, &pathSize ) ) { -// gchar * utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL ); -// if ( !g_utf8_validate(utf8Path, -1, NULL) ) { -// g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" ); -// } else { -// sources.pushback(utf8Path); -// } -// g_free( utf8Path ); -// } -// #endif // WIN32 +#ifdef WIN32 + wchar_t pathBuf[MAX_PATH + 1]; + pathBuf[0] = 0; + DWORD pathSize = sizeof(pathBuf); + g_assert(sizeof(wchar_t) == sizeof(gunichar2)); + if ( GetColorDirectoryW( NULL, pathBuf, &pathSize ) ) { + gchar * utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL ); + if ( !g_utf8_validate(utf8Path, -1, NULL) ) { + g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" ); + } else { + sources.push_back(utf8Path); + } + g_free( utf8Path ); + } +#endif // WIN32 return sources; } diff --git a/src/connector-context.cpp b/src/connector-context.cpp index c8754972a..2131bdced 100644 --- a/src/connector-context.cpp +++ b/src/connector-context.cpp @@ -409,7 +409,7 @@ sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, G { spcc_reset_colors(cc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); } if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) { // Doing simething else like rerouting. @@ -532,8 +532,6 @@ connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const // Test whether we clicked on a connection point cc->sid = conn_pt_handle_test(cc, p); - sp_event_context_snap_window_open(event_context); - if (!cc->sid) { // This is the first point, so just snap it to the grid // as there's no other points to go off. @@ -560,7 +558,7 @@ connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const } cc_set_active_conn(cc, cc->newconn); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); ret = TRUE; break; } @@ -579,7 +577,7 @@ connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const cc_connector_rerouting_finish(cc, &p); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); // Don't set ret to TRUE, so we drop through to the // parent handler which will open the context menu. @@ -587,7 +585,7 @@ connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const else if (cc->npoints != 0) { spcc_connector_finish(cc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); ret = TRUE; } } @@ -719,7 +717,7 @@ connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton con } cc_set_active_conn(cc, cc->newconn); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); break; } case SP_CONNECTOR_CONTEXT_REROUTING: @@ -729,7 +727,7 @@ connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton con sp_document_ensure_up_to_date(doc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); return TRUE; break; } @@ -757,7 +755,7 @@ connector_handle_key_press(SPConnectorContext *const cc, guint const keyval) if (cc->npoints != 0) { spcc_connector_finish(cc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc)); + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); ret = TRUE; } break; @@ -772,7 +770,7 @@ connector_handle_key_press(SPConnectorContext *const cc, guint const keyval) sp_document_undo(doc); cc->state = SP_CONNECTOR_CONTEXT_IDLE; - sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc)); + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE, _("Connector endpoint drag cancelled.")); ret = TRUE; @@ -780,7 +778,7 @@ connector_handle_key_press(SPConnectorContext *const cc, guint const keyval) else if (cc->npoints != 0) { // if drawing, cancel, otherwise pass it up for deselecting cc->state = SP_CONNECTOR_CONTEXT_STOP; - sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc)); + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc)); spcc_reset_colors(cc); ret = TRUE; } @@ -1089,7 +1087,6 @@ endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc) cc->clickedhandle = cc->active_handle; cc_clear_active_conn(cc); cc->state = SP_CONNECTOR_CONTEXT_REROUTING; - sp_event_context_snap_window_open(SP_EVENT_CONTEXT(cc)); // Disconnect from attached shape unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1; diff --git a/src/desktop-events.cpp b/src/desktop-events.cpp index cc2d49330..cea478f85 100644 --- a/src/desktop-events.cpp +++ b/src/desktop-events.cpp @@ -66,13 +66,11 @@ int sp_desktop_root_handler(SPCanvasItem */*item*/, GdkEvent *event, SPDesktop * return sp_event_context_root_handler(desktop->event_context, event); } - static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw, bool horiz) { static bool dragging = false; static SPCanvasItem *guide = NULL; static Geom::Point normal; - static bool snap_window_temporarily_open = false; int wx, wy; SPDesktop *desktop = dtw->desktop; @@ -91,16 +89,6 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge // FIXME: The snap delay mechanism won't work here, because it has been implemented for the event context. Dragging // guides off the ruler will send event to the ruler and not to the context, which bypasses sp_event_context_snap_delay_handler - // The snap manager will not notice the difference, so it'll check if the snap delay has been activated (This check - // is only needed for catching coding errors, i.e. to warn if the snap window has not been implemented properly - // in some context) - if (desktop->event_context->_snap_window_open == false) { - // A dt_ruler_event might be emitted when dragging a guide off the rulers while drawing a Bezier curve - // In such a situation, we're already in that specific context and the snap delay is already active. We should - // not set the snap delay to active again, because that will trigger a similar warning to the one above - sp_event_context_snap_window_open(desktop->event_context); - snap_window_temporarily_open = true; - } Geom::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win)); Geom::Point const event_dt(desktop->w2d(event_w)); @@ -159,9 +147,10 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); - // We only have a temporary guide which is not stored in our document yet. Because the guide snapper only looks - // in the document for guides to snap to, we don't have to worry about a guide snapping to itself here - m.guideFreeSnap(event_dt, normal); + // We only have a temporary guide which is not stored in our document yet. + // Because the guide snapper only looks in the document for guides to snap to, + // we don't have to worry about a guide snapping to itself here + m.guideFreeSnap(event_dt, normal, SP_DRAG_MOVE_ORIGIN); sp_guideline_set_position(SP_GUIDELINE(guide), from_2geom(event_dt)); desktop->set_coordinate_status(to_2geom(event_dt)); @@ -176,17 +165,14 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); - // We only have a temporary guide which is not stored in our document yet. Because the guide snapper only looks - // in the document for guides to snap to, we don't have to worry about a guide snapping to itself here - m.guideFreeSnap(event_dt, normal); + // We only have a temporary guide which is not stored in our document yet. + // Because the guide snapper only looks in the document for guides to snap to, + // we don't have to worry about a guide snapping to itself here + m.guideFreeSnap(event_dt, normal, SP_DRAG_MOVE_ORIGIN); dragging = false; - // See the comments in GDK_BUTTON_PRESS - if (snap_window_temporarily_open) { - sp_event_context_snap_window_closed(desktop->event_context); - snap_window_temporarily_open = false; - } + sp_event_context_discard_delayed_snap_event(desktop->event_context); gtk_object_destroy(GTK_OBJECT(guide)); guide = NULL; @@ -202,11 +188,13 @@ static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidge } desktop->set_coordinate_status(from_2geom(event_dt)); - // A dt_ruler_event might be emitted when dragging a guide of the rulers while drawing a Bezier curve - // In such a situation, we're already in that specific context and the snap delay is already active. We should - // interfere with that context and we should therefore leave the snap delay status as it is. So although it might - // have been set to active above on GDK_BUTTON_PRESS, we should not set it back to inactive here. That must be - // done by the context + // A dt_ruler_event might be emitted when dragging a guide of the rulers + // while drawing a Bezier curve. In such a situation, we're already in that + // specific context and the snap delay is already active. We should interfere + // with that context and we should therefore leave the snap delay status + // as it is. So although it might have been set to active above on + // GDK_BUTTON_PRESS, we should not set it back to inactive here. That must be + // done by the context. } default: break; @@ -225,20 +213,13 @@ int sp_dt_vruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw) return sp_dt_ruler_event(widget, event, dtw, false); } -/* Guides */ -enum SPGuideDragType { - SP_DRAG_TRANSLATE, - SP_DRAG_TRANSLATE_CONSTRAINED, // Is not being used currently! - SP_DRAG_ROTATE, - SP_DRAG_MOVE_ORIGIN, - SP_DRAG_NONE -}; - static Geom::Point drag_origin; static SPGuideDragType drag_type = SP_DRAG_NONE; +//static bool reset_drag_origin = false; // when Ctrl is pressed while dragging, this is used to trigger resetting of the +// // drag origin to that location so that constrained movement is more intuitive // Min distance from anchor to initiate rotation, measured in screenpixels -#define tol 40.0 +#define tol 40.0 gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) { @@ -252,7 +233,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) case GDK_2BUTTON_PRESS: if (event->button.button == 1) { drag_type = SP_DRAG_NONE; - sp_event_context_snap_window_closed(desktop->event_context); + sp_event_context_discard_delayed_snap_event(desktop->event_context); sp_canvas_item_ungrab(item, event->button.time); Inkscape::UI::Dialogs::GuidelinePropertiesDialog::showDialog(guide, desktop); ret = TRUE; @@ -260,15 +241,6 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) break; case GDK_BUTTON_PRESS: if (event->button.button == 1) { - if (event->button.state & GDK_CONTROL_MASK && !(event->button.state & GDK_SHIFT_MASK)) { - SPDocument *doc = SP_OBJECT_DOCUMENT(guide); - sp_guide_remove(guide); - sp_document_done(doc, SP_VERB_NONE, _("Delete guide")); - ret = TRUE; - break; - } - - sp_event_context_snap_window_open(desktop->event_context); Geom::Point const event_w(event->button.x, event->button.y); Geom::Point const event_dt(desktop->w2d(event_w)); @@ -279,11 +251,18 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) // https://bugs.launchpad.net/inkscape/+bug/333762 drag_origin = Geom::projection(event_dt, Geom::Line(guide->point_on_line, guide->angle())); - if (Geom::L2(guide->point_on_line - event_dt) < tol/desktop->current_zoom()) { - // the click was on the guide 'anchor' - drag_type = (event->button.state & GDK_SHIFT_MASK) ? SP_DRAG_MOVE_ORIGIN : SP_DRAG_TRANSLATE; + if (event->button.state & GDK_SHIFT_MASK) { + // with shift we rotate the guide + drag_type = SP_DRAG_ROTATE; } else { - drag_type = (event->button.state & GDK_SHIFT_MASK) ? SP_DRAG_ROTATE : SP_DRAG_TRANSLATE; + if (event->button.state & GDK_CONTROL_MASK) { + drag_type = SP_DRAG_MOVE_ORIGIN; + } else { + drag_type = SP_DRAG_TRANSLATE; + } + } + + if (drag_type == SP_DRAG_ROTATE || drag_type == SP_DRAG_TRANSLATE) { sp_canvas_item_grab(item, ( GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | @@ -305,32 +284,29 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, NULL, NULL, guide); if (drag_type == SP_DRAG_MOVE_ORIGIN) { - // If we snap in guideConstrainedSnap() below, then motion_dt will be forced to be on the guide - // If we don't snap however, then it the origin should still be constrained to the guide - // So let's do that explicitly first: - Geom::Line line(guide->point_on_line, guide->angle()); - Geom::Coord t = line.nearestPoint(motion_dt); - motion_dt = line.pointAt(t); - m.guideConstrainedSnap(motion_dt, *guide); + // If we snap in guideConstrainedSnap() below, then motion_dt will + // be forced to be on the guide. If we don't snap however, then + // the origin should still be constrained to the guide. So let's do + // that explicitly first: + + Geom::Line line(guide->point_on_line, guide->angle()); + Geom::Coord t = line.nearestPoint(motion_dt); + motion_dt = line.pointAt(t); + m.guideConstrainedSnap(motion_dt, *guide); } else { - m.guideFreeSnap(motion_dt, guide->normal_to_line); + m.guideFreeSnap(motion_dt, guide->normal_to_line, drag_type); } switch (drag_type) { case SP_DRAG_TRANSLATE: { - sp_guide_moveto(*guide, guide->point_on_line + motion_dt - drag_origin, false); - break; - } - case SP_DRAG_TRANSLATE_CONSTRAINED: // Is not being used currently! - { - Geom::Point pt_constr = Geom::constrain_angle(guide->point_on_line, motion_dt); - sp_guide_moveto(*guide, pt_constr, false); + sp_guide_moveto(*guide, motion_dt, false); break; } case SP_DRAG_ROTATE: { - double angle = angle_between(drag_origin - guide->point_on_line, motion_dt - guide->point_on_line); + Geom::Point pt = motion_dt - guide->point_on_line; + double angle = std::atan2(pt[Geom::Y], pt[Geom::X]); if (event->motion.state & GDK_CONTROL_MASK) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); @@ -339,7 +315,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) angle = (M_PI / snaps) * sections; } } - sp_guide_set_normal(*guide, guide->normal_to_line * Geom::Rotate(angle), false); + sp_guide_set_normal(*guide, Geom::Point(1,0) * Geom::Rotate(angle + M_PI_2), false); break; } case SP_DRAG_MOVE_ORIGIN: @@ -368,33 +344,29 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, NULL, NULL, guide); if (drag_type == SP_DRAG_MOVE_ORIGIN) { - // If we snap in guideConstrainedSnap() below, then motion_dt will be forced to be on the guide - // If we don't snap however, then it the origin should still be constrained to the guide - // So let's do that explicitly first: + // If we snap in guideConstrainedSnap() below, then motion_dt will + // be forced to be on the guide. If we don't snap however, then + // the origin should still be constrained to the guide. So let's + // do that explicitly first: Geom::Line line(guide->point_on_line, guide->angle()); - Geom::Coord t = line.nearestPoint(event_dt); - event_dt = line.pointAt(t); + Geom::Coord t = line.nearestPoint(event_dt); + event_dt = line.pointAt(t); m.guideConstrainedSnap(event_dt, *guide); - } else { - m.guideFreeSnap(event_dt, guide->normal_to_line); - } + } else { + m.guideFreeSnap(event_dt, guide->normal_to_line, drag_type); + } if (sp_canvas_world_pt_inside_window(item->canvas, event_w)) { switch (drag_type) { case SP_DRAG_TRANSLATE: { - sp_guide_moveto(*guide, guide->point_on_line + event_dt - drag_origin, true); - break; - } - case SP_DRAG_TRANSLATE_CONSTRAINED: // Is not being used currently! - { - Geom::Point pt_constr = Geom::constrain_angle(guide->point_on_line, event_dt); - sp_guide_moveto(*guide, pt_constr, true); + sp_guide_moveto(*guide, event_dt, true); break; } case SP_DRAG_ROTATE: { - double angle = angle_between(drag_origin - guide->point_on_line, event_dt - guide->point_on_line); + Geom::Point pt = event_dt - guide->point_on_line; + double angle = std::atan2(pt[Geom::Y], pt[Geom::X]); if (event->motion.state & GDK_CONTROL_MASK) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); @@ -403,7 +375,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) angle = (M_PI / snaps) * sections; } } - sp_guide_set_normal(*guide, guide->normal_to_line * Geom::Rotate(angle), true); + sp_guide_set_normal(*guide, Geom::Point(1,0) * Geom::Rotate(angle + M_PI_2), true); break; } case SP_DRAG_MOVE_ORIGIN: @@ -430,7 +402,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) desktop->setPosition (from_2geom(event_dt)); } drag_type = SP_DRAG_NONE; - sp_event_context_snap_window_closed(desktop->event_context); + sp_event_context_discard_delayed_snap_event(desktop->event_context); sp_canvas_item_ungrab(item, event->button.time); ret=TRUE; } @@ -442,14 +414,12 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) Geom::Point const event_w(event->crossing.x, event->crossing.y); Geom::Point const event_dt(desktop->w2d(event_w)); - GdkCursor *guide_cursor; - if (Geom::L2(guide->point_on_line - event_dt) < tol/desktop->current_zoom() || !(event->crossing.state & GDK_SHIFT_MASK)) { - // the click was on the guide 'anchor' - guide_cursor = gdk_cursor_new (GDK_FLEUR); - } else { + if (event->crossing.state & GDK_SHIFT_MASK) { + GdkCursor *guide_cursor; guide_cursor = gdk_cursor_new (GDK_EXCHANGE); + gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, guide_cursor); + gdk_cursor_unref(guide_cursor); } - gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, guide_cursor); char *guide_description = sp_guide_description(guide); desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("<b>Guideline</b>: %s"), guide_description); @@ -464,8 +434,45 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) desktop->guidesMessageContext()->clear(); break; - default: + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_Delete: + case GDK_KP_Delete: + case GDK_BackSpace: + { + SPDocument *doc = SP_OBJECT_DOCUMENT(guide); + sp_guide_remove(guide); + sp_document_done(doc, SP_VERB_NONE, _("Delete guide")); + ret = TRUE; + break; + } + case GDK_Shift_L: + case GDK_Shift_R: + GdkCursor *guide_cursor; + guide_cursor = gdk_cursor_new (GDK_EXCHANGE); + gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, guide_cursor); + gdk_cursor_unref(guide_cursor); + ret = TRUE; + default: + // do nothing; + break; + } break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_Shift_L: + case GDK_Shift_R: + GdkCursor *guide_cursor; + guide_cursor = gdk_cursor_new (GDK_EXCHANGE); + gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, guide_cursor); + gdk_cursor_unref(guide_cursor); + break; + default: + // do nothing; + break; + } + default: + break; } return ret; diff --git a/src/desktop.cpp b/src/desktop.cpp index 6b7c20094..319a0d407 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -148,7 +148,6 @@ SPDesktop::SPDesktop() : _layer_hierarchy( 0 ), _reconstruction_old_layer_id( 0 ), _display_mode(Inkscape::RENDERMODE_NORMAL), - _saved_display_mode(Inkscape::RENDERMODE_NORMAL), _widget( 0 ), _inkscape( 0 ), _guides_message_context( 0 ), @@ -165,8 +164,10 @@ SPDesktop::SPDesktop() : } void -SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas) +SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas, Inkscape::UI::View::EditWidgetInterface *widget) { + _widget = widget; + // Temporary workaround for link order issues: Inkscape::DeviceManager::getManager().getDevices(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -226,11 +227,18 @@ SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas) setDisplayModeNormal(); } + // The order in which these canvas items are added determines the z-order. It's therefore + // important to add the tempgroup (which will contain the snapindicator) before adding the + // controls. Only this way one will be able to quickly (before the snap indicator has + // disappeared) reselect a node after snapping it. If the z-order is wrong however, this + // will not work (the snap indicator is on top of the node handler; is the snapindicator + // being selected? or does it intercept some of the events that should have gone to the + // node handler? see bug https://bugs.launchpad.net/inkscape/+bug/414142) gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); - controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); /* Push select tool to the bottom of stack */ /** \todo @@ -437,18 +445,21 @@ void SPDesktop::_setDisplayMode(Inkscape::RenderMode mode) { SP_CANVAS_ARENA (drawing)->arena->rendermode = mode; canvas->rendermode = mode; _display_mode = mode; - if (mode != Inkscape::RENDERMODE_OUTLINE) { - _saved_display_mode = _display_mode; - } sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this))); } void SPDesktop::displayModeToggle() { - if (_display_mode == Inkscape::RENDERMODE_OUTLINE) { - _setDisplayMode(_saved_display_mode); - } else { + switch (_display_mode) { + case Inkscape::RENDERMODE_NORMAL: + _setDisplayMode(Inkscape::RENDERMODE_NO_FILTERS); + break; + case Inkscape::RENDERMODE_NO_FILTERS: _setDisplayMode(Inkscape::RENDERMODE_OUTLINE); + break; + case Inkscape::RENDERMODE_OUTLINE: + default: + _setDisplayMode(Inkscape::RENDERMODE_NORMAL); } } diff --git a/src/desktop.h b/src/desktop.h index 73b9262dd..cfb977425 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -180,7 +180,7 @@ struct SPDesktop : public Inkscape::UI::View::View #endif SPDesktop(); - void init (SPNamedView* nv, SPCanvas* canvas); + void init (SPNamedView* nv, SPCanvas* canvas, Inkscape::UI::View::EditWidgetInterface *widget); virtual ~SPDesktop(); void destroy(); @@ -203,7 +203,6 @@ struct SPDesktop : public Inkscape::UI::View::View } void displayModeToggle(); Inkscape::RenderMode _display_mode; - Inkscape::RenderMode _saved_display_mode; Inkscape::RenderMode getMode() const { return _display_mode; } Inkscape::UI::Widget::Dock* getDock() { return _widget->getDock(); } @@ -304,9 +303,6 @@ struct SPDesktop : public Inkscape::UI::View::View void fullscreen(); void focusMode(bool mode = true); - void registerEditWidget (Inkscape::UI::View::EditWidgetInterface *widget) - { _widget = widget; } - Geom::Matrix w2d() const; //transformation from window to desktop coordinates (used for zooming) Geom::Point w2d(Geom::Point const &p) const; Geom::Point d2w(Geom::Point const &p) const; diff --git a/src/dialogs/export.cpp b/src/dialogs/export.cpp index 4e235bbf5..2c04135bc 100644 --- a/src/dialogs/export.cpp +++ b/src/dialogs/export.cpp @@ -60,6 +60,11 @@ #include "helper/png-write.h" +#ifdef WIN32 +#include <windows.h> +#include <COMMDLG.h> +#include <gdk/gdkwin32.h> +#endif #define SP_EXPORT_MIN_SIZE 1.0 @@ -167,7 +172,7 @@ sp_export_dialog_delete ( GtkObject */*object*/, GdkEvent */*event*/, gpointer / if (x<0) x=0; if (y<0) y=0; - + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setInt(prefs_path + "x", x); prefs->setInt(prefs_path + "y", y); @@ -535,8 +540,7 @@ sp_export_dialog (void) gchar *name; SPDocument * doc = SP_ACTIVE_DOCUMENT; const gchar *uri = SP_DOCUMENT_URI (doc); - Inkscape::XML::Node * repr = sp_document_repr_root(doc); - const gchar * text_extension = repr->attribute("inkscape:output_extension"); + const gchar *text_extension = get_file_save_extension (Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS).c_str(); Inkscape::Extension::Output * oextension = NULL; if (text_extension != NULL) { @@ -1056,6 +1060,23 @@ filename_add_extension (const gchar *filename, const gchar *extension) } } +gchar *absolutize_path_from_document_location (SPDocument *doc, const gchar *filename) +{ + gchar *path = 0; + //Make relative paths go from the document location, if possible: + if (!g_path_is_absolute(filename) && doc->uri) { + gchar *dirname = g_path_get_dirname(doc->uri); + if (dirname) { + path = g_build_filename(dirname, filename, NULL); + g_free(dirname); + } + } + if (!path) { + path = g_strdup(filename); + } + return path; +} + /// Called when export button is clicked static void sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) @@ -1063,6 +1084,7 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) if (!SP_ACTIVE_DESKTOP) return; SPNamedView *nv = sp_desktop_namedview(SP_ACTIVE_DESKTOP); + SPDocument *doc = sp_desktop_document (SP_ACTIVE_DESKTOP); GtkWidget *be = (GtkWidget *)gtk_object_get_data(base, "batch_checkbox"); GtkWidget *he = (GtkWidget *)gtk_object_get_data(base, "hide_checkbox"); @@ -1084,10 +1106,14 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) i != NULL; i = i->next) { SPItem *item = (SPItem *) i->data; + // retrieve export filename hint - const gchar *fn = SP_OBJECT_REPR(item)->attribute("inkscape:export-filename"); - if (!fn) { - fn = create_filepath_from_id (SP_OBJECT_ID(item), NULL); + const gchar *filename = SP_OBJECT_REPR(item)->attribute("inkscape:export-filename"); + gchar *path = 0; + if (!filename) { + path = create_filepath_from_id (SP_OBJECT_ID(item), NULL); + } else { + path = absolutize_path_from_document_location(doc, filename); } // retrieve export dpi hints @@ -1108,14 +1134,14 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) if (width > 1 && height > 1) { /* Do export */ - if (!sp_export_png_file (sp_desktop_document (SP_ACTIVE_DESKTOP), fn, + if (!sp_export_png_file (doc, path, *area, width, height, dpi, dpi, nv->pagecolor, NULL, NULL, TRUE, // overwrite without asking hide ? (GSList *) sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList() : NULL )) { gchar * error; - gchar * safeFile = Inkscape::IO::sanitizeString(fn); + gchar * safeFile = Inkscape::IO::sanitizeString(path); error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile); sp_ui_error_dialog(error); g_free(safeFile); @@ -1124,6 +1150,7 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) } } n++; + g_free(path); sp_export_progress_callback((float)n/num, base); } @@ -1154,7 +1181,13 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) return; } - gchar *dirname = g_path_get_dirname(filename); + // make sure that .png is the extension of the file: + gchar * filename_ext = filename_add_extension(filename, "png"); + gtk_entry_set_text(GTK_ENTRY(fe), filename_ext); + + gchar *path = absolutize_path_from_document_location(doc, filename_ext); + + gchar *dirname = g_path_get_dirname(path); if ( dirname == NULL || !Inkscape::IO::file_test(dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) ) { @@ -1165,30 +1198,27 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) g_free(safeDir); g_free(error); g_free(dirname); + g_free(path); return; } g_free(dirname); - // make sure that .png is the extension of the file: - gchar * filename_ext = filename_add_extension(filename, "png"); - gtk_entry_set_text(GTK_ENTRY(fe), filename_ext); - - gchar *fn = g_path_get_basename (filename_ext); - + gchar *fn = g_path_get_basename (path); gchar *progress_text = g_strdup_printf (_("Exporting %s (%lu x %lu)"), fn, width, height); g_free (fn); + GtkWidget *prog_dlg = create_progress_dialog (base, progress_text); g_free (progress_text); /* Do export */ - if (!sp_export_png_file (sp_desktop_document (SP_ACTIVE_DESKTOP), filename_ext, + if (!sp_export_png_file (sp_desktop_document (SP_ACTIVE_DESKTOP), path, Geom::Rect(Geom::Point(x0, y0), Geom::Point(x1, y1)), width, height, xdpi, ydpi, nv->pagecolor, sp_export_progress_callback, base, FALSE, hide ? (GSList *) sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList() : NULL )) { gchar * error; - gchar * safeFile = Inkscape::IO::sanitizeString(filename); + gchar * safeFile = Inkscape::IO::sanitizeString(path); error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile); sp_ui_error_dialog(error); g_free(safeFile); @@ -1284,12 +1314,14 @@ sp_export_export_clicked (GtkButton */*button*/, GtkObject *base) } g_free (filename_ext); + g_free (path); } } // end of sp_export_export_clicked() /// Called when Browse button is clicked +/// @todo refactor this code to use ui/dialogs/filedialog.cpp static void sp_export_browse_clicked (GtkButton */*button*/, gpointer /*userdata*/) { @@ -1318,11 +1350,59 @@ sp_export_browse_clicked (GtkButton */*button*/, gpointer /*userdata*/) filename = gtk_entry_get_text (GTK_ENTRY (fe)); if (*filename == '\0') { - filename = homedir_path(NULL); + filename = create_filepath_from_id(NULL, NULL); } gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (fs), filename); +#ifdef WIN32 + // code in this section is borrowed from ui/dialogs/filedialogimpl-win32.cpp + OPENFILENAMEW opf; + WCHAR filter_string[20]; + wcsncpy(filter_string, L"PNG#*.png##", 11); + filter_string[3] = L'\0'; + filter_string[9] = L'\0'; + filter_string[10] = L'\0'; + WCHAR* title_string = (WCHAR*)g_utf8_to_utf16(_("Select a filename for exporting"), -1, NULL, NULL, NULL); + WCHAR* extension_string = (WCHAR*)g_utf8_to_utf16("*.png", -1, NULL, NULL, NULL); + // Copy the selected file name, converting from UTF-8 to UTF-16 + WCHAR _filename[_MAX_PATH + 1]; + memset(_filename, 0, sizeof(_filename)); + gunichar2* utf16_path_string = g_utf8_to_utf16(filename, -1, NULL, NULL, NULL); + wcsncpy(_filename, (wchar_t*)utf16_path_string, _MAX_PATH); + g_free(utf16_path_string); + + opf.hwndOwner = (HWND)(GDK_WINDOW_HWND(GTK_WIDGET(dlg)->window)); + opf.lpstrFilter = filter_string; + opf.lpstrCustomFilter = 0; + opf.nMaxCustFilter = 0L; + opf.nFilterIndex = 1L; + opf.lpstrFile = _filename; + opf.nMaxFile = _MAX_PATH; + opf.lpstrFileTitle = NULL; + opf.nMaxFileTitle=0; + opf.lpstrInitialDir = 0; + opf.lpstrTitle = title_string; + opf.nFileOffset = 0; + opf.nFileExtension = 2; + opf.lpstrDefExt = extension_string; + opf.lpfnHook = NULL; + opf.lCustData = 0; + opf.Flags = OFN_PATHMUSTEXIST; + opf.lStructSize = sizeof(OPENFILENAMEW); + if (GetSaveFileNameW(&opf) != 0) + { + // Copy the selected file name, converting from UTF-16 to UTF-8 + gchar *utf8string = g_utf16_to_utf8((const gunichar2*)opf.lpstrFile, _MAX_PATH, NULL, NULL, NULL); + gtk_entry_set_text (GTK_ENTRY (fe), utf8string); + g_object_set_data (G_OBJECT (dlg), "filename", fe); + g_free(utf8string); + + } + g_free(extension_string); + g_free(title_string); + +#else if (gtk_dialog_run (GTK_DIALOG (fs)) == GTK_RESPONSE_ACCEPT) { gchar *file; @@ -1337,6 +1417,7 @@ sp_export_browse_clicked (GtkButton */*button*/, gpointer /*userdata*/) g_free(utf8file); g_free(file); } +#endif gtk_widget_destroy (fs); diff --git a/src/dialogs/item-properties.cpp b/src/dialogs/item-properties.cpp index 44a479b6c..211b800dc 100644 --- a/src/dialogs/item-properties.cpp +++ b/src/dialogs/item-properties.cpp @@ -19,7 +19,7 @@ #include <gtk/gtkhbox.h> #include <gtk/gtktable.h> #include <gtk/gtkcheckbutton.h> -#include <gtk/gtkspinbutton.h> +#include <gtk/gtk.h> #include <gtk/gtklabel.h> #include <gtk/gtkframe.h> #include <gtk/gtkexpander.h> diff --git a/src/display/canvas-axonomgrid.cpp b/src/display/canvas-axonomgrid.cpp index 71b22d2ad..a92e7cf5b 100644 --- a/src/display/canvas-axonomgrid.cpp +++ b/src/display/canvas-axonomgrid.cpp @@ -311,7 +311,7 @@ CanvasAxonomGrid::readRepr() if ( (value = repr->attribute("spacingy")) ) { sp_nv_read_length(value, SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE, &lengthy, &gridunit); lengthy = sp_units_get_pixels(lengthy, *(gridunit)); - if (lengthy < 1.0) lengthy = 1.0; + if (lengthy < 0.0500) lengthy = 0.0500; } if ( (value = repr->attribute("gridanglex")) ) { diff --git a/src/display/curve-test.h b/src/display/curve-test.h index d779fe09b..784ccee89 100644 --- a/src/display/curve-test.h +++ b/src/display/curve-test.h @@ -90,7 +90,7 @@ public: pv[0] = path1; TS_ASSERT_EQUALS(SPCurve(pv).nodes_in_path() , 3u); pv[0] = path2; - TS_ASSERT_EQUALS(SPCurve(pv).nodes_in_path() , 3u); + TS_ASSERT_EQUALS(SPCurve(pv).nodes_in_path() , 2u); // zero length closing segments do not increase the nodecount. pv[0] = path3; TS_ASSERT_EQUALS(SPCurve(pv).nodes_in_path() , 5u); pv[0] = path4; diff --git a/src/display/curve.cpp b/src/display/curve.cpp index 5a1e2abeb..7d7dbc987 100644 --- a/src/display/curve.cpp +++ b/src/display/curve.cpp @@ -629,10 +629,14 @@ SPCurve::nodes_in_path() const nr++; // count last node (this works also for closed paths because although they don't have a 'last node', they do have an extra segment - if (it->closed()) { + // do not count closing knot double for zero-length closing line segments + // however, if the path is only a moveto, and is closed, do not subtract 1 (otherwise the result will be zero nodes) + if ( it->closed() + && ((*it).size() != 0) ) + { Geom::Curve const &c = it->back_closed(); if (are_near(c.initialPoint(), c.finalPoint())) { - nr--; // do not count closing knot double for zero-length closing line segments + nr--; } } } diff --git a/src/display/nr-filter-image.cpp b/src/display/nr-filter-image.cpp index 9679e56e7..2b799f8d2 100644 --- a/src/display/nr-filter-image.cpp +++ b/src/display/nr-filter-image.cpp @@ -107,6 +107,13 @@ int FilterImage::render(FilterSlot &slot, FilterUnits const &units) { if (!image_pixbuf){ try { + /* TODO: If feImageHref is absolute, then use that (preferably handling the + * case that it's not a file URI). Otherwise, go up the tree looking + * for an xml:base attribute, and use that as the base URI for resolving + * the relative feImageHref URI. Otherwise, if document && document->base, + * then use that as the base URI. Otherwise, use feImageHref directly + * (i.e. interpreting it as relative to our current working directory). + * (See http://www.w3.org/TR/xmlbase/#resolution .) */ gchar *fullname = feImageHref; if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) { // Try to load from relative postion combined with document base diff --git a/src/display/nr-filter.cpp b/src/display/nr-filter.cpp index af432bdf3..d0e0ec11e 100644 --- a/src/display/nr-filter.cpp +++ b/src/display/nr-filter.cpp @@ -52,7 +52,7 @@ #if defined (SOLARIS) && (SOLARIS == 8) #include "round.h" using Inkscape::round; -#endif +#endif namespace Inkscape { namespace Filters { @@ -92,9 +92,9 @@ Filter::Filter() Filter::Filter(int n) { _primitive_count = 0; - _primitive_table_size = n; - _primitive = new FilterPrimitive*[n]; - for ( int i = 0 ; i < n ; i++ ) { + _primitive_table_size = (n > 0) ? n : 1; // we guarantee there is at least 1(one) filter slot + _primitive = new FilterPrimitive*[_primitive_table_size]; + for ( int i = 0 ; i < _primitive_table_size ; i++ ) { _primitive[i] = NULL; } _common_init(); @@ -159,7 +159,7 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb) // It's no use to try and filter an empty object. return 1; } - + FilterUnits units(_filter_units, _primitive_units); units.set_ctm(trans); units.set_item_bbox(item_bbox); @@ -168,6 +168,8 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb) // TODO: with filterRes of 0x0 should return an empty image std::pair<double,double> resolution = _filter_resolution(filter_area, trans, filterquality); + if(!(resolution.first > 0 && resolution.second > 0)) + return 1; units.set_resolution(resolution.first, resolution.second); if (_x_pixels > 0) { units.set_automatic_resolution(false); @@ -370,7 +372,11 @@ void Filter::_enlarge_primitive_table() { for (int i = _primitive_count ; i < _primitive_table_size ; i++) { new_tbl[i] = NULL; } - delete[] _primitive; + if(_primitive != NULL) { + delete[] _primitive; + } else { + g_warning("oh oh"); + } _primitive = new_tbl; } @@ -434,7 +440,7 @@ void Filter::clear_primitives() } void Filter::set_x(SVGLength const &length) -{ +{ if (length._set) _region_x = length; } @@ -449,7 +455,7 @@ void Filter::set_width(SVGLength const &length) _region_width = length; } void Filter::set_height(SVGLength const &length) -{ +{ if (length._set) _region_height = length; } diff --git a/src/display/snap-indicator.cpp b/src/display/snap-indicator.cpp index 081496fd0..20ea7d58c 100644 --- a/src/display/snap-indicator.cpp +++ b/src/display/snap-indicator.cpp @@ -26,8 +26,8 @@ namespace Display { SnapIndicator::SnapIndicator(SPDesktop * desktop) : _snaptarget(NULL), - _snaptarget_tooltip(NULL), - _snapsource(NULL), + _snaptarget_tooltip(NULL), + _snapsource(NULL), _desktop(desktop) { } @@ -35,14 +35,14 @@ SnapIndicator::SnapIndicator(SPDesktop * desktop) SnapIndicator::~SnapIndicator() { // remove item that might be present - remove_snaptarget(); - remove_snapsource(); + remove_snaptarget(); + remove_snapsource(); } void SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const p) { - remove_snaptarget(); //only display one snaptarget at a time + remove_snaptarget(); //only display one snaptarget at a time g_assert(_desktop != NULL); @@ -59,191 +59,194 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const p) // TRANSLATORS: undefined target for snapping gchar *target_name = _("UNDEFINED"); switch (p.getTarget()) { - case SNAPTARGET_UNDEFINED: - target_name = _("UNDEFINED"); - break; - case SNAPTARGET_GRID: - target_name = _("grid line"); - break; + case SNAPTARGET_UNDEFINED: + target_name = _("UNDEFINED"); + break; + case SNAPTARGET_GRID: + target_name = _("grid line"); + break; case SNAPTARGET_GRID_INTERSECTION: - target_name = _("grid intersection"); - break; + target_name = _("grid intersection"); + break; case SNAPTARGET_GUIDE: - target_name = _("guide"); - break; + target_name = _("guide"); + break; case SNAPTARGET_GUIDE_INTERSECTION: - target_name = _("guide intersection"); - break; + target_name = _("guide intersection"); + break; + case SNAPTARGET_GUIDE_ORIGIN: + target_name = _("guide origin"); + break; case SNAPTARGET_GRID_GUIDE_INTERSECTION: - target_name = _("grid-guide intersection"); - break; + target_name = _("grid-guide intersection"); + break; case SNAPTARGET_NODE_CUSP: - target_name = _("cusp node"); - break; + target_name = _("cusp node"); + break; case SNAPTARGET_NODE_SMOOTH: - target_name = _("smooth node"); - break; - case SNAPTARGET_PATH: - target_name = _("path"); - break; + target_name = _("smooth node"); + break; + case SNAPTARGET_PATH: + target_name = _("path"); + break; case SNAPTARGET_PATH_INTERSECTION: - target_name = _("path intersection"); - break; + target_name = _("path intersection"); + break; case SNAPTARGET_BBOX_CORNER: - target_name = _("bounding box corner"); - break; + target_name = _("bounding box corner"); + break; case SNAPTARGET_BBOX_EDGE: - target_name = _("bounding box side"); - break; + target_name = _("bounding box side"); + break; case SNAPTARGET_GRADIENTS_PARENT_BBOX: - target_name = _("bounding box"); - break; + target_name = _("bounding box"); + break; case SNAPTARGET_PAGE_BORDER: - target_name = _("page border"); - break; + target_name = _("page border"); + break; case SNAPTARGET_LINE_MIDPOINT: - target_name = _("line midpoint"); - break; + target_name = _("line midpoint"); + break; case SNAPTARGET_OBJECT_MIDPOINT: - target_name = _("object midpoint"); - break; + target_name = _("object midpoint"); + break; case SNAPTARGET_ROTATION_CENTER: - target_name = _("object rotation center"); - break; + target_name = _("object rotation center"); + break; case SNAPTARGET_HANDLE: - target_name = _("handle"); - break; + target_name = _("handle"); + break; case SNAPTARGET_BBOX_EDGE_MIDPOINT: - target_name = _("bounding box side midpoint"); - break; + target_name = _("bounding box side midpoint"); + break; case SNAPTARGET_BBOX_MIDPOINT: - target_name = _("bounding box midpoint"); - break; + target_name = _("bounding box midpoint"); + break; case SNAPTARGET_PAGE_CORNER: - target_name = _("page corner"); - break; + target_name = _("page corner"); + break; case SNAPTARGET_CONVEX_HULL_CORNER: - target_name = _("convex hull corner"); - break; + target_name = _("convex hull corner"); + break; case SNAPTARGET_ELLIPSE_QUADRANT_POINT: - target_name = _("quadrant point"); - break; + target_name = _("quadrant point"); + break; case SNAPTARGET_CENTER: - target_name = _("center"); - break; + target_name = _("center"); + break; case SNAPTARGET_CORNER: - target_name = _("corner"); - break; + target_name = _("corner"); + break; case SNAPTARGET_TEXT_BASELINE: - target_name = _("text baseline"); - break; + target_name = _("text baseline"); + break; default: - g_warning("Snap target has not yet been defined!"); + g_warning("Snap target has not yet been defined!"); break; } gchar *source_name = _("UNDEFINED"); - switch (p.getSource()) { - case SNAPSOURCE_UNDEFINED: - source_name = _("UNDEFINED"); - break; - case SNAPSOURCE_BBOX_CORNER: - source_name = _("Bounding box corner"); - break; - case SNAPSOURCE_BBOX_MIDPOINT: - source_name = _("Bounding box midpoint"); - break; - case SNAPSOURCE_BBOX_EDGE_MIDPOINT: - source_name = _("Bounding box side midpoint"); - break; - case SNAPSOURCE_NODE_SMOOTH: - source_name = _("Smooth node"); - break; - case SNAPSOURCE_NODE_CUSP: - source_name = _("Cusp node"); - break; - case SNAPSOURCE_LINE_MIDPOINT: - source_name = _("Line midpoint"); - break; - case SNAPSOURCE_OBJECT_MIDPOINT: - source_name = _("Object midpoint"); - break; - case SNAPSOURCE_ROTATION_CENTER: - source_name = _("Object rotation center"); - break; - case SNAPSOURCE_HANDLE: - source_name = _("Handle"); - break; - case SNAPSOURCE_PATH_INTERSECTION: - source_name = _("Path intersection"); - break; - case SNAPSOURCE_GUIDE: - source_name = _("Guide"); - break; - case SNAPSOURCE_GUIDE_ORIGIN: - source_name = _("Guide origin"); - break; - case SNAPSOURCE_CONVEX_HULL_CORNER: - source_name = _("Convex hull corner"); - break; - case SNAPSOURCE_ELLIPSE_QUADRANT_POINT: - source_name = _("Quadrant point"); - break; - case SNAPSOURCE_CENTER: - source_name = _("Center"); - break; - case SNAPSOURCE_CORNER: - source_name = _("Corner"); - break; - case SNAPSOURCE_TEXT_BASELINE: - source_name = _("Text baseline"); - break; - default: - g_warning("Snap source has not yet been defined!"); - break; - } + switch (p.getSource()) { + case SNAPSOURCE_UNDEFINED: + source_name = _("UNDEFINED"); + break; + case SNAPSOURCE_BBOX_CORNER: + source_name = _("Bounding box corner"); + break; + case SNAPSOURCE_BBOX_MIDPOINT: + source_name = _("Bounding box midpoint"); + break; + case SNAPSOURCE_BBOX_EDGE_MIDPOINT: + source_name = _("Bounding box side midpoint"); + break; + case SNAPSOURCE_NODE_SMOOTH: + source_name = _("Smooth node"); + break; + case SNAPSOURCE_NODE_CUSP: + source_name = _("Cusp node"); + break; + case SNAPSOURCE_LINE_MIDPOINT: + source_name = _("Line midpoint"); + break; + case SNAPSOURCE_OBJECT_MIDPOINT: + source_name = _("Object midpoint"); + break; + case SNAPSOURCE_ROTATION_CENTER: + source_name = _("Object rotation center"); + break; + case SNAPSOURCE_HANDLE: + source_name = _("Handle"); + break; + case SNAPSOURCE_PATH_INTERSECTION: + source_name = _("Path intersection"); + break; + case SNAPSOURCE_GUIDE: + source_name = _("Guide"); + break; + case SNAPSOURCE_GUIDE_ORIGIN: + source_name = _("Guide origin"); + break; + case SNAPSOURCE_CONVEX_HULL_CORNER: + source_name = _("Convex hull corner"); + break; + case SNAPSOURCE_ELLIPSE_QUADRANT_POINT: + source_name = _("Quadrant point"); + break; + case SNAPSOURCE_CENTER: + source_name = _("Center"); + break; + case SNAPSOURCE_CORNER: + source_name = _("Corner"); + break; + case SNAPSOURCE_TEXT_BASELINE: + source_name = _("Text baseline"); + break; + default: + g_warning("Snap source has not yet been defined!"); + break; + } //std::cout << "Snapped " << source_name << " to " << target_name << std::endl; - remove_snapsource(); // Don't set both the source and target indicators, as these will overlap + remove_snapsource(); // Don't set both the source and target indicators, as these will overlap // Display the snap indicator (i.e. the cross) SPCanvasItem * canvasitem = NULL; - if (p.getTarget() == SNAPTARGET_NODE_SMOOTH || p.getTarget() == SNAPTARGET_NODE_CUSP) { - canvasitem = sp_canvas_item_new(sp_desktop_tempgroup (_desktop), - SP_TYPE_CTRL, - "anchor", GTK_ANCHOR_CENTER, - "size", 10.0, - "stroked", TRUE, - "stroke_color", 0xf000f0ff, - "mode", SP_KNOT_MODE_XOR, - "shape", SP_KNOT_SHAPE_DIAMOND, - NULL ); - } else { - canvasitem = sp_canvas_item_new(sp_desktop_tempgroup (_desktop), - SP_TYPE_CTRL, - "anchor", GTK_ANCHOR_CENTER, - "size", 10.0, - "stroked", TRUE, - "stroke_color", 0xf000f0ff, - "mode", SP_KNOT_MODE_XOR, - "shape", SP_KNOT_SHAPE_CROSS, - NULL ); - } + if (p.getTarget() == SNAPTARGET_NODE_SMOOTH || p.getTarget() == SNAPTARGET_NODE_CUSP) { + canvasitem = sp_canvas_item_new(sp_desktop_tempgroup (_desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "size", 10.0, + "stroked", TRUE, + "stroke_color", 0xf000f0ff, + "mode", SP_KNOT_MODE_XOR, + "shape", SP_KNOT_SHAPE_DIAMOND, + NULL ); + } else { + canvasitem = sp_canvas_item_new(sp_desktop_tempgroup (_desktop), + SP_TYPE_CTRL, + "anchor", GTK_ANCHOR_CENTER, + "size", 10.0, + "stroked", TRUE, + "stroke_color", 0xf000f0ff, + "mode", SP_KNOT_MODE_XOR, + "shape", SP_KNOT_SHAPE_CROSS, + NULL ); + } - const int timeout_val = 1200; // TODO add preference for snap indicator timeout? + const int timeout_val = 1200; // TODO add preference for snap indicator timeout? - SP_CTRL(canvasitem)->moveto(p.getPoint()); - _snaptarget = _desktop->add_temporary_canvasitem(canvasitem, timeout_val); + SP_CTRL(canvasitem)->moveto(p.getPoint()); + _snaptarget = _desktop->add_temporary_canvasitem(canvasitem, timeout_val); - gchar *tooltip_str = g_strconcat(source_name, _(" to "), target_name, NULL); - Geom::Point tooltip_pos = p.getPoint() + _desktop->w2d(Geom::Point(15, -15)); + gchar *tooltip_str = g_strconcat(source_name, _(" to "), target_name, NULL); + Geom::Point tooltip_pos = p.getPoint() + _desktop->w2d(Geom::Point(15, -15)); - SPCanvasItem *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(_desktop), _desktop, tooltip_pos, tooltip_str); - g_free(tooltip_str); + SPCanvasItem *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(_desktop), _desktop, tooltip_pos, tooltip_str); + g_free(tooltip_str); - sp_canvastext_set_anchor((SPCanvasText* )canvas_tooltip, -1, 1); - _snaptarget_tooltip = _desktop->add_temporary_canvasitem(canvas_tooltip, timeout_val); - } + sp_canvastext_set_anchor((SPCanvasText* )canvas_tooltip, -1, 1); + _snaptarget_tooltip = _desktop->add_temporary_canvasitem(canvas_tooltip, timeout_val); + } } void @@ -255,16 +258,16 @@ SnapIndicator::remove_snaptarget() } if (_snaptarget_tooltip) { - _desktop->remove_temporary_canvasitem(_snaptarget_tooltip); - _snaptarget_tooltip = NULL; - } + _desktop->remove_temporary_canvasitem(_snaptarget_tooltip); + _snaptarget_tooltip = NULL; + } } void SnapIndicator::set_new_snapsource(std::pair<Geom::Point, int> const p) { - remove_snapsource(); + remove_snapsource(); g_assert(_desktop != NULL); @@ -284,7 +287,7 @@ SnapIndicator::set_new_snapsource(std::pair<Geom::Point, int> const p) SP_CTRL(canvasitem)->moveto(p.first); _snapsource = _desktop->add_temporary_canvasitem(canvasitem, 1000); - } + } } void diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index 664713582..e640911d6 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -1494,7 +1494,7 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event) int retval = FALSE; - /* dispatch normally regardless of the event's window if an item has + /* dispatch normally regardless of the event's window if an item has a pointer grab in effect */ if (!canvas->grabbed_item && event->window != SP_CANVAS_WINDOW (canvas)) diff --git a/src/document.cpp b/src/document.cpp index 288e52c6d..d406f3712 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -224,6 +224,14 @@ void SPDocument::remove_persp3d (Persp3D * const /*persp*/) g_print ("Please implement deletion of perspectives here.\n"); } +void SPDocument::initialize_current_persp3d() +{ + this->current_persp3d = persp3d_document_first_persp(this); + if (!this->current_persp3d) { + this->current_persp3d = persp3d_create_xml_element(this); + } +} + unsigned long SPDocument::serial() const { return priv->serial; } @@ -373,11 +381,7 @@ sp_document_create(Inkscape::XML::Document *rdoc, // 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->current_persp3d = persp3d_document_first_persp(document); - if (!document->current_persp3d) { - document->current_persp3d = persp3d_create_xml_element (document); - } + document->initialize_current_persp3d(); sp_document_set_undo_sensitive(document, true); @@ -730,6 +734,10 @@ 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; } @@ -900,16 +908,16 @@ 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 32 iterations. So we bail out if we hit 32 iterations, + * more than 64 iterations. So we bail out if we hit 64 iterations, * since this typically indicates we're stuck in an update loop. */ gint sp_document_ensure_up_to_date(SPDocument *doc) { - int counter = 32; + int counter = 64; while (!doc->_updateDocument()) { if (counter == 0) { - g_warning("More than 32 iteration while updating document '%s'", doc->uri); + g_warning("More than 64 iteration while updating document '%s'", doc->uri? doc->uri:"<unknown URI, probably clipboard>"); break; } counter--; diff --git a/src/document.h b/src/document.h index 696e568ad..789e3e2ed 100644 --- a/src/document.h +++ b/src/document.h @@ -114,6 +114,7 @@ struct SPDocument : public Inkscape::GC::Managed<>, void add_persp3d(Persp3D * const persp); void remove_persp3d(Persp3D * const persp); + void initialize_current_persp3d(); sigc::connection connectModified(ModifiedSignal::slot_type slot); sigc::connection connectURISet(URISetSignal::slot_type slot); diff --git a/src/draw-context.cpp b/src/draw-context.cpp index d2794f0c2..de9a7c7e5 100644 --- a/src/draw-context.cpp +++ b/src/draw-context.cpp @@ -841,7 +841,7 @@ void spdc_create_single_dot(SPEventContext *ec, Geom::Point const &pt, char cons Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Geom::Matrix const i2d (sp_item_i2d_affine (item)); - Geom::Point pp = pt * i2d; + Geom::Point pp = pt; double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0); if (event_state & GDK_MOD1_MASK) { /* TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size @@ -860,6 +860,7 @@ void spdc_create_single_dot(SPEventContext *ec, Geom::Point const &pt, char cons sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width); sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width); item->updateRepr(); + sp_item_set_item_transform(item, i2d.inverse()); sp_desktop_selection(desktop)->set(item); diff --git a/src/dropper-context.cpp b/src/dropper-context.cpp index 985e3ac51..aa17ea859 100644 --- a/src/dropper-context.cpp +++ b/src/dropper-context.cpp @@ -129,6 +129,11 @@ static void sp_dropper_context_finish(SPEventContext *ec) SPDropperContext *dc = SP_DROPPER_CONTEXT(ec); ec->enableGrDrag(false); + + if (dc->grabbed) { + sp_canvas_item_ungrab(dc->grabbed, GDK_CURRENT_TIME); + dc->grabbed = NULL; + } if (dc->area) { gtk_object_destroy(GTK_OBJECT(dc->area)); @@ -171,6 +176,12 @@ static gint sp_dropper_context_root_handler(SPEventContext *event_context, GdkEv dc->dragging = TRUE; ret = TRUE; } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + dc->grabbed = SP_CANVAS_ITEM(desktop->acetate); + break; case GDK_MOTION_NOTIFY: if (event->motion.state & GDK_BUTTON2_MASK) { @@ -311,6 +322,11 @@ static gint sp_dropper_context_root_handler(SPEventContext *event_context, GdkEv { sp_canvas_item_hide(dc->area); dc->dragging = FALSE; + + if (dc->grabbed) { + sp_canvas_item_ungrab(dc->grabbed, event->button.time); + dc->grabbed = NULL; + } double alpha_to_set = setalpha? dc->alpha : 1.0; diff --git a/src/dropper-context.h b/src/dropper-context.h index 678ab6b47..64181e3c8 100644 --- a/src/dropper-context.h +++ b/src/dropper-context.h @@ -32,6 +32,7 @@ struct SPDropperContext { unsigned int dragging : 1; + SPCanvasItem *grabbed; SPCanvasItem *area; Geom::Point centre; diff --git a/src/ege-adjustment-action.cpp b/src/ege-adjustment-action.cpp index 942328100..e6ec392ad 100644 --- a/src/ege-adjustment-action.cpp +++ b/src/ege-adjustment-action.cpp @@ -47,7 +47,7 @@ #include <gdk/gdkkeysyms.h> #include <gtk/gtkversion.h> #include <gtk/gtktoolitem.h> -#include <gtk/gtkspinbutton.h> +#include <gtk/gtk.h> #include <gtk/gtkhscale.h> #if GTK_CHECK_VERSION(2,12,0) #include <gtk/gtkscalebutton.h> diff --git a/src/ege-select-one-action.cpp b/src/ege-select-one-action.cpp index 5c552a6e7..6de22495d 100644 --- a/src/ege-select-one-action.cpp +++ b/src/ege-select-one-action.cpp @@ -44,7 +44,7 @@ #include <gtk/gtkhbox.h> #include <gtk/gtklabel.h> #include <gtk/gtktoolitem.h> -#include <gtk/gtkcombobox.h> +#include <gtk/gtk.h> #include <gtk/gtkcellrenderertext.h> #include <gtk/gtkcellrendererpixbuf.h> #include <gtk/gtkcelllayout.h> diff --git a/src/event-context.cpp b/src/event-context.cpp index b1cfac518..9b846f2b7 100644 --- a/src/event-context.cpp +++ b/src/event-context.cpp @@ -374,7 +374,9 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, switch (event->button.button) { case 1: if (event_context->space_panning) { - 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); @@ -382,10 +384,12 @@ static gint sp_event_context_private_root_handler(SPEventContext *event_context, } break; case 2: - if (event->button.state == GDK_SHIFT_MASK) { + if (event->button.state & GDK_SHIFT_MASK) { zoom_rb = 2; } else { - 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); @@ -395,7 +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) { - 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); @@ -415,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; @@ -874,9 +880,8 @@ sp_event_context_activate(SPEventContext *ec) // Make sure no delayed snapping events are carried over after switching contexts // (this is only an additional safety measure against sloppy coding, because each - // context should take care of this by itself. It might be hard to get each and every - // context perfect though) - sp_event_context_snap_window_closed(ec, false); + // context should take care of this by itself. + sp_event_context_discard_delayed_snap_event(ec); if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate) ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate(ec); @@ -901,8 +906,7 @@ sp_event_context_deactivate(SPEventContext *ec) gint sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event) { - //std::cout << "sp_event_context_root_handler" << std::endl; - switch (event->type) { + switch (event->type) { case GDK_MOTION_NOTIFY: sp_event_context_snap_delay_handler(event_context, NULL, NULL, (GdkEventMotion *)event, DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER); break; @@ -929,8 +933,6 @@ sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event) gint sp_event_context_virtual_root_handler(SPEventContext * event_context, GdkEvent * event) { - //std::cout << "sp_event_context_virtual_root_handler -> postponed: " << event_context->desktop->namedview->snap_manager.snapprefs.getSnapPostponedGlobally() << std::endl; - gint ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event); set_event_location(event_context->desktop, event); return ret; @@ -942,7 +944,6 @@ sp_event_context_virtual_root_handler(SPEventContext * event_context, GdkEvent * gint sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event) { - //std::cout << "sp_event_context_item_handler" << std::endl; switch (event->type) { case GDK_MOTION_NOTIFY: sp_event_context_snap_delay_handler(event_context, item, NULL, (GdkEventMotion *)event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER); @@ -1180,22 +1181,23 @@ void sp_event_context_snap_delay_handler(SPEventContext *ec, SPItem* const item, 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 - - if (c1 || c2) { + 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 - delete ec->_delayed_snap_event; - ec->_delayed_snap_event = NULL; - } - - if (ec->_snap_window_open && !c1 && !c2 && 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. + 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 + ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(true); // put snapping on hold - Geom::Point event_pos(event->x, event->y); + Geom::Point event_pos(event->x, event->y); guint32 event_t = gdk_event_get_time ( (GdkEvent *) event ); if (prev_pos) { @@ -1232,7 +1234,7 @@ void sp_event_context_snap_delay_handler(SPEventContext *ec, SPItem* const item, prev_pos = event_pos; prev_time = event_t; - } + } } gboolean sp_event_context_snap_watchdog_callback(gpointer data) @@ -1286,51 +1288,8 @@ gboolean sp_event_context_snap_watchdog_callback(gpointer data) return FALSE; //Kills the timer and stops it from executing this callback over and over again. } -void sp_event_context_snap_window_open(SPEventContext *ec, bool show_debug_warnings) -{ - // Only when ec->_snap_window_open has been set, Inkscape will know that snapping is active - // and will delay any snapping events (but only when asked to through the preferences) - - // When snapping is being delayed, then that will also mean that at some point the last event - // might be re-triggered. This should only occur when Inkscape is still in the same tool or context, - // and even more specifically, the tool should even be in the same state. If for example snapping is being delayed while - // creating a rectangle, then the rect-context will be active and it will be in the "dragging" state - // (see the static boolean variable "dragging" in the sp_rect_context_root_handler). The procedure is - // as follows: call sp_event_context_snap_window_open(*, TRUE) when entering the "dragging" state, which will delay - // snapping from that moment on, and call sp_event_context_snap_window_open(*, FALSE) when leaving the "dragging" - // state. This last call will also make sure that any pending snap events will be canceled. - - //std::cout << "sp_event_context_snap_window_open" << std::endl; - if (!ec) { - if (show_debug_warnings) { - g_warning("sp_event_context_snap_window_open() has been called without providing an event context!"); - } - return; - } - - if (ec->_snap_window_open == true && show_debug_warnings) { - g_warning("Snap window was already open! This is a bug, please report it."); - } - - ec->_snap_window_open = true; -} - -void sp_event_context_snap_window_closed(SPEventContext *ec, bool show_debug_warnings) +void sp_event_context_discard_delayed_snap_event(SPEventContext *ec) { - //std::cout << "sp_event_context_snap_window_closed" << std::endl; - if (!ec) { - if (show_debug_warnings) { - g_warning("sp_event_context_snap_window_closed() has been called without providing an event context!"); - } - return; - } - - if (ec->_snap_window_open == false && show_debug_warnings) { - g_warning("Snap window was already closed! This is a bug, please report it."); - } - - ec->_snap_window_open = false; - delete ec->_delayed_snap_event; ec->_delayed_snap_event = NULL; } diff --git a/src/event-context.h b/src/event-context.h index 638ac56f7..5285bdb87 100644 --- a/src/event-context.h +++ b/src/event-context.h @@ -40,8 +40,7 @@ namespace Inkscape { } gboolean sp_event_context_snap_watchdog_callback(gpointer data); -void sp_event_context_snap_window_open(SPEventContext *ec, bool show_debug_warnings = true); -void sp_event_context_snap_window_closed(SPEventContext *ec, bool show_debug_warnings = true); +void sp_event_context_discard_delayed_snap_event(SPEventContext *ec); class DelayedSnapEvent { @@ -123,7 +122,6 @@ struct SPEventContext : public GObject { bool space_panning; - bool _snap_window_open; DelayedSnapEvent *_delayed_snap_event; }; diff --git a/src/extension/execution-env.cpp b/src/extension/execution-env.cpp index 4a13890d7..e8d7c4baf 100644 --- a/src/extension/execution-env.cpp +++ b/src/extension/execution-env.cpp @@ -131,7 +131,7 @@ ExecutionEnv::createWorkingDialog (void) { return; Gtk::Window *window = Glib::wrap(GTK_WINDOW(toplevel), false); - gchar * dlgmessage = g_strdup_printf(_("'%s' working, please wait..."), _effect->get_name()); + gchar * dlgmessage = g_strdup_printf(_("'%s' working, please wait..."), _(_effect->get_name())); _visibleDialog = new Gtk::MessageDialog(*window, dlgmessage, false, // use markup diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp index eabf147f6..5f1bef8d1 100644 --- a/src/extension/implementation/script.cpp +++ b/src/extension/implementation/script.cpp @@ -63,7 +63,7 @@ namespace Extension { namespace Implementation { /** \brief Make GTK+ events continue to come through a little bit - + This just keeps coming the events through so that we'll make the GUI update and look pretty. */ @@ -477,7 +477,7 @@ ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) : Inkscape::Extension::save( Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), - view->doc(), _filename.c_str(), false, false, false); + view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); return; } @@ -604,6 +604,7 @@ Script::open(Inkscape::Extension::Input *module, \param module Extention to be used \param doc Document to be saved \param filename The name to save the final file as + \return false in case of any failure writing the file, otherwise true Well, at some point people need to save - it is really what makes the entire application useful. And, it is possible that someone @@ -634,17 +635,19 @@ Script::save(Inkscape::Extension::Output *module, tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg"); } catch (...) { /// \todo Popup dialog here - return; + throw Inkscape::Extension::Output::save_failed(); } if (helper_extension.size() == 0) { Inkscape::Extension::save( Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), - doc, tempfilename_in.c_str(), false, false, false); + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); } else { Inkscape::Extension::save( Inkscape::Extension::db.get(helper_extension.c_str()), - doc, tempfilename_in.c_str(), false, false, false); + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); } @@ -652,13 +655,17 @@ Script::save(Inkscape::Extension::Output *module, execute(command, params, tempfilename_in, fileout); std::string lfilename = Glib::filename_from_utf8(filenameArg); - fileout.toFile(lfilename); + bool success = fileout.toFile(lfilename); // make sure we don't leak file descriptors from g_file_open_tmp close(tempfd_in); // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name unlink(tempfilename_in.c_str()); + if(success == false) { + throw Inkscape::Extension::Output::save_failed(); + } + return; } @@ -709,8 +716,6 @@ Script::effect(Inkscape::Extension::Effect *module, SPDesktop *desktop = (SPDesktop *)doc; sp_namedview_document_from_window(desktop); - gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension")); - std::list<std::string> params; module->paramListString(params); @@ -774,10 +779,7 @@ Script::effect(Inkscape::Extension::Effect *module, doc->doc()->emitReconstructionFinish(); mydoc->release(); sp_namedview_update_layers_from_document(desktop); - - sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension); } - g_free(orig_output_extension); return; } @@ -840,7 +842,7 @@ Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot) { using Inkscape::Util::List; - using Inkscape::XML::AttributeRecord; + using Inkscape::XML::AttributeRecord; std::vector<gchar const *> attribs; // Make a list of all attributes of the old root node. @@ -1007,7 +1009,7 @@ Script::execute (const std::list<std::string> &in_command, for (std::list<std::string>::const_iterator i = in_params.begin(); i != in_params.end(); i++) { //g_message("Script parameter: %s",(*i)g.c_str()); - argv.push_back(*i); + argv.push_back(*i); } if (!(filein.empty())) { diff --git a/src/extension/implementation/script.h b/src/extension/implementation/script.h index 4620375f9..8e25fb351 100644 --- a/src/extension/implementation/script.h +++ b/src/extension/implementation/script.h @@ -135,7 +135,7 @@ private: /** * */ - void checkStderr (const Glib::ustring &filename, + void checkStderr (const Glib::ustring &filename, Gtk::MessageType type, const Glib::ustring &message); @@ -146,7 +146,7 @@ private: Glib::RefPtr<Glib::IOChannel> _channel; Glib::RefPtr<Glib::MainLoop> _main_loop; bool _dead; - + public: file_listener () : _dead(false) { }; virtual ~file_listener () { @@ -187,11 +187,15 @@ private: // Note, doing a copy here, on purpose Glib::ustring string (void) { return _string; }; - void toFile (const Glib::ustring &name) { + bool toFile (const Glib::ustring &name) { + try { Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w"); stdout_file->set_encoding(); stdout_file->write(_string); - return; + } catch (Glib::FileError &e) { + return false; + } + return true; }; }; diff --git a/src/extension/input.cpp b/src/extension/input.cpp index 689c1286f..b4599dbd0 100644 --- a/src/extension/input.cpp +++ b/src/extension/input.cpp @@ -138,12 +138,6 @@ Input::check (void) from a file. The first thing that this does is make sure that the file actually exists. If it doesn't, a NULL is returned. If the file exits, then it is opened using the implmentation of this extension. - - After opening the document the output_extension is set. What this - accomplishes is that save can try to use an extension that supports - the same fileformat. So something like opening and saveing an - Adobe Illustrator file can be transparent (not recommended, but - transparent). This is all done with undo being turned off. */ SPDocument * Input::open (const gchar *uri) @@ -157,13 +151,6 @@ Input::open (const gchar *uri) timer->touch(); SPDocument *const doc = imp->open(this, uri); - if (doc != NULL) { - Inkscape::XML::Node * repr = sp_document_repr_root(doc); - bool saved = sp_document_get_undo_sensitive(doc); - sp_document_set_undo_sensitive (doc, false); - repr->setAttribute("inkscape:output_extension", output_extension); - sp_document_set_undo_sensitive (doc, saved); - } return doc; } diff --git a/src/extension/internal/bitmap/imagemagick.cpp b/src/extension/internal/bitmap/imagemagick.cpp index ab2834141..e907612fd 100644 --- a/src/extension/internal/bitmap/imagemagick.cpp +++ b/src/extension/internal/bitmap/imagemagick.cpp @@ -129,6 +129,7 @@ ImageMagickDocCache::readImage(const char *xlink, Magick::Image *image) image->read(path); } catch (...) {} } + g_free(search); } bool diff --git a/src/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp index dff89c1ed..9ac19326f 100644 --- a/src/extension/internal/cairo-ps-out.cpp +++ b/src/extension/internal/cairo-ps-out.cpp @@ -156,9 +156,9 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con new_bitmapResolution = mod->get_param_int("resolution"); } catch(...) {} - bool new_areaCanvas = true; + bool new_areaPage = true; try { - new_areaCanvas = mod->get_param_bool("areaCanvas"); + new_areaPage = mod->get_param_bool("areaPage"); } catch(...) {} bool new_areaDrawing = true; @@ -173,7 +173,7 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con gchar * final_name; final_name = g_strdup_printf("> %s", filename); - ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, new_blurToBitmap, new_bitmapResolution, new_exportId, new_areaDrawing, new_areaCanvas); + ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, new_blurToBitmap, new_bitmapResolution, new_exportId, new_areaDrawing, new_areaPage); g_free(final_name); if (!ret) @@ -220,9 +220,9 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co new_bitmapResolution = mod->get_param_int("resolution"); } catch(...) {} - bool new_areaCanvas = true; + bool new_areaPage = true; try { - new_areaCanvas = mod->get_param_bool("areaCanvas"); + new_areaPage = mod->get_param_bool("areaPage"); } catch(...) {} bool new_areaDrawing = true; @@ -237,7 +237,7 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co gchar * final_name; final_name = g_strdup_printf("> %s", filename); - ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, new_blurToBitmap, new_bitmapResolution, new_exportId, new_areaDrawing, new_areaCanvas, true); + ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, new_blurToBitmap, new_bitmapResolution, new_exportId, new_areaDrawing, new_areaPage, true); g_free(final_name); if (!ret) @@ -279,11 +279,11 @@ CairoPsOutput::init (void) "<_item value='PS2'>" N_("PostScript level 2") "</_item>\n" #endif "</param>\n" - "<param name=\"areaCanvas\" gui-text=\"" N_("Export area is whole canvas") "\" type=\"boolean\">true</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is the drawing") "\" type=\"boolean\">true</param>\n" "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi)") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" + "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">true</param>\n" + "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">true</param>\n" "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID") "\" type=\"string\"></param>\n" "<output>\n" "<extension>.ps</extension>\n" @@ -316,11 +316,11 @@ CairoEpsOutput::init (void) "<_item value='PS2'>" N_("PostScript level 2") "</_item>\n" #endif "</param>\n" - "<param name=\"areaCanvas\" gui-text=\"" N_("Export area is whole canvas") "\" type=\"boolean\">true</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is the drawing") "\" type=\"boolean\">true</param>\n" "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi)") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" + "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">true</param>\n" + "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">true</param>\n" "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID") "\" type=\"string\"></param>\n" "<output>\n" "<extension>.eps</extension>\n" diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index d1462e52e..c33beab8a 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -125,14 +125,27 @@ CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _renderer(parent), _render_mode(RENDER_MODE_NORMAL), _clip_mode(CLIP_MODE_MASK) -{} +{ + font_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, font_data_free); +} CairoRenderContext::~CairoRenderContext(void) { + if(font_table != NULL) { + g_hash_table_remove_all(font_table); + } + if (_cr) cairo_destroy(_cr); if (_surface) cairo_surface_destroy(_surface); if (_layout) g_object_unref(_layout); } +void CairoRenderContext::font_data_free(gpointer data) +{ + cairo_font_face_t *font_face = (cairo_font_face_t *)data; + if (font_face) { + cairo_font_face_destroy(font_face); + } +} CairoRenderer* CairoRenderContext::getRenderer(void) const @@ -192,6 +205,8 @@ CairoRenderContext::cloneMe(double width, double height) const (int)ceil(width), (int)ceil(height)); new_context->_cr = cairo_create(surface); new_context->_surface = surface; + new_context->_width = width; + new_context->_height = height; new_context->_is_valid = TRUE; return new_context; @@ -610,7 +625,7 @@ CairoRenderContext::popLayer(void) // copy over the correct CTM // It must be stored in item_transform of current state after pushState. - Geom::Matrix item_transform; + Geom::Matrix item_transform; if (_state->parent_has_userspace) item_transform = getParentState()->transform * _state->item_transform; else @@ -749,7 +764,12 @@ CairoRenderContext::setupSurface(double width, double height) if (_vector_based_target && _stream == NULL) return false; + _width = width; + _height = height; + cairo_surface_t *surface = NULL; + cairo_matrix_t ctm; + cairo_matrix_init_identity (&ctm); switch (_target) { case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(_target_format, (int)ceil(width), (int)ceil(height)); @@ -762,10 +782,10 @@ CairoRenderContext::setupSurface(double width, double height) #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { return FALSE; } +#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) cairo_ps_surface_restrict_to_level (surface, (cairo_ps_level_t)_ps_level); cairo_ps_surface_set_eps (surface, (cairo_bool_t) _eps); #endif @@ -776,7 +796,7 @@ CairoRenderContext::setupSurface(double width, double height) break; } - return _finishSurfaceSetup (surface); + return _finishSurfaceSetup (surface, &ctm); } bool @@ -796,13 +816,16 @@ bool CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm) { if(surface == NULL) { - return FALSE; + return false; } if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { - return FALSE; + return false; } _cr = cairo_create(surface); + if(CAIRO_STATUS_SUCCESS != cairo_status(_cr)) { + return false; + } if (ctm) cairo_set_matrix(_cr, ctm); _surface = surface; @@ -1005,7 +1028,7 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver } - // Calculate the size of the surface which has to be created + // Calculate the size of the surface which has to be created #define SUBPIX_SCALE 100 // Cairo requires an integer pattern surface width/height. // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel. @@ -1313,7 +1336,7 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con } bool no_fill = style->fill.isNone() || style->fill_opacity.value == 0; - bool no_stroke = style->stroke.isNone() || style->stroke_width.computed < 1e-9 || + bool no_stroke = style->stroke.isNone() || style->stroke_width.computed < 1e-9 || style->stroke_opacity.value == 0; if (no_fill && no_stroke) @@ -1440,7 +1463,7 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi #define GLYPH_ARRAY_SIZE 64 unsigned int -CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> const &glyphtext, bool is_stroke) +CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> const &glyphtext, bool path) { cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE]; cairo_glyph_t *glyphs = glyph_array; @@ -1464,15 +1487,10 @@ CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoG i++; } - if (is_stroke) { + if (path) { cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs); } else { - if (_is_texttopath) { - cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs); - cairo_fill_preserve(cr); - } else { - cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs); - } + cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs); } if (num_glyphs > GLYPH_ARRAY_SIZE) @@ -1487,10 +1505,11 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma { // create a cairo_font_face from PangoFont double size = style->font_size.computed; - cairo_font_face_t *font_face = NULL; + gpointer fonthash = (gpointer)font; + cairo_font_face_t *font_face = (cairo_font_face_t *)g_hash_table_lookup(font_table, fonthash); FcPattern *fc_pattern = NULL; - + #ifdef USE_PANGO_WIN32 # ifdef CAIRO_HAS_WIN32_FONT LOGFONTA *lfa = pango_win32_font_logfont(font); @@ -1499,17 +1518,23 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma ZeroMemory(&lfw, sizeof(LOGFONTW)); memcpy(&lfw, lfa, sizeof(LOGFONTA)); MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE); - - font_face = cairo_win32_font_face_create_for_logfontw(&lfw); + + if(font_face == NULL) { + font_face = cairo_win32_font_face_create_for_logfontw(&lfw); + g_hash_table_insert(font_table, fonthash, font_face); + } # endif #else # ifdef CAIRO_HAS_FT_FONT PangoFcFont *fc_font = PANGO_FC_FONT(font); fc_pattern = fc_font->font_pattern; - font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); + if(font_face == NULL) { + font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); + g_hash_table_insert(font_table, fonthash, font_face); + } # endif #endif - + cairo_save(_cr); cairo_set_font_face(_cr, font_face); @@ -1534,28 +1559,36 @@ CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Matrix const *font_ma _showGlyphs(_cr, font, glyphtext, TRUE); } } else { - + bool fill = false, stroke = false, have_path = false; if (style->fill.isColor() || style->fill.isPaintserver()) { - // set fill style - _setFillStyle(style, NULL); - - _showGlyphs(_cr, font, glyphtext, FALSE); + fill = true; } if (style->stroke.isColor() || style->stroke.isPaintserver()) { - // set stroke style + stroke = true; + } + if (fill) { + _setFillStyle(style, NULL); + if (_is_texttopath) { + _showGlyphs(_cr, font, glyphtext, true); + have_path = true; + if (stroke) cairo_fill_preserve(_cr); + else cairo_fill(_cr); + } else { + _showGlyphs(_cr, font, glyphtext, false); + } + } + if (stroke) { _setStrokeStyle(style, NULL); - - // paint stroke - _showGlyphs(_cr, font, glyphtext, TRUE); + if (!have_path) _showGlyphs(_cr, font, glyphtext, true); cairo_stroke(_cr); } } cairo_restore(_cr); - if (font_face) - cairo_font_face_destroy(font_face); +// if (font_face) +// cairo_font_face_destroy(font_face); return true; } diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h index 930668e03..e6f2d698e 100644 --- a/src/extension/internal/cairo-render-context.h +++ b/src/extension/internal/cairo-render-context.h @@ -195,6 +195,9 @@ protected: void _concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0); void _concatTransform(cairo_t *cr, Geom::Matrix const *transform); + GHashTable *font_table; + static void font_data_free(gpointer data); + CairoRenderState *_createState(void); }; diff --git a/src/extension/internal/cairo-renderer-pdf-out.cpp b/src/extension/internal/cairo-renderer-pdf-out.cpp index b44e83449..0598c388a 100644 --- a/src/extension/internal/cairo-renderer-pdf-out.cpp +++ b/src/extension/internal/cairo-renderer-pdf-out.cpp @@ -180,7 +180,7 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, bool new_exportCanvas = FALSE; try { - new_exportCanvas = mod->get_param_bool("areaCanvas"); + new_exportCanvas = mod->get_param_bool("areaPage"); } catch(...) { g_warning("Parameter <exportCanvas> might not exist"); @@ -219,8 +219,8 @@ CairoRendererPdfOutput::init (void) "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi)") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export drawing, not page") "\" type=\"boolean\">false</param>\n" - "<param name=\"areaCanvas\" gui-text=\"" N_("Export canvas") "\" type=\"boolean\">false</param>\n" + "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">false</param>\n" + "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">false</param>\n" "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID") "\" type=\"string\"></param>\n" "<output>\n" "<extension>.pdf</extension>\n" diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp index da88a5eae..8cc386135 100644 --- a/src/extension/internal/cairo-renderer.cpp +++ b/src/extension/internal/cairo-renderer.cpp @@ -192,11 +192,28 @@ static void sp_shape_render (SPItem *item, CairoRenderContext *ctx) ctx->renderPathVector(pathv, style, &pbox); - for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - // START position - for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START - if ( shape->marker[i] ) { - SPMarker* marker = SP_MARKER (shape->marker[i]); + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( shape->marker[i] ) { + SPMarker* marker = SP_MARKER (shape->marker[i]); + Geom::Matrix tr; + if (marker->orient_auto) { + tr = sp_shape_marker_get_transform_at_start(pathv.begin()->front()); + } else { + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(pathv.begin()->front().pointAt(0)); + } + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + } + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + if ( !shape->marker[i] ) continue; + SPMarker* marker = SP_MARKER (shape->marker[i]); + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there + { Geom::Matrix tr; if (marker->orient_auto) { tr = sp_shape_marker_get_transform_at_start(path_it->front()); @@ -205,11 +222,8 @@ static void sp_shape_render (SPItem *item, CairoRenderContext *ctx) } sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); } - } - - // MID position - for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID - if ( shape->marker[i] && (path_it->size_default() > 1) ) { + // MID position + if (path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) @@ -217,9 +231,6 @@ static void sp_shape_render (SPItem *item, CairoRenderContext *ctx) /* Put marker between curve_it1 and curve_it2. * Loop to end_default (so including closing segment), because when a path is closed, * there should be a midpoint marker between last segment and closing straight line segment */ - - SPMarker* marker = SP_MARKER (shape->marker[i]); - Geom::Matrix tr; if (marker->orient_auto) { tr = sp_shape_marker_get_transform(*curve_it1, *curve_it2); @@ -233,32 +244,43 @@ static void sp_shape_render (SPItem *item, CairoRenderContext *ctx) ++curve_it2; } } - } - - // END position - for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END - if ( shape->marker[i] ) { - SPMarker* marker = SP_MARKER (shape->marker[i]); - - /* Get reference to last curve in the path. - * For moveto-only path, this returns the "closing line segment". */ - unsigned int index = path_it->size_default(); - if (index > 0) { - index--; - } - Geom::Curve const &lastcurve = (*path_it)[index]; - + // END position + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); Geom::Matrix tr; if (marker->orient_auto) { tr = sp_shape_marker_get_transform_at_end(lastcurve); } else { tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(lastcurve.pointAt(1)); } - sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); } } } + // END marker + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( shape->marker[i] ) { + SPMarker* marker = SP_MARKER (shape->marker[i]); + + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; + + Geom::Matrix tr; + if (marker->orient_auto) { + tr = sp_shape_marker_get_transform_at_end(lastcurve); + } else { + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(lastcurve.pointAt(1)); + } + + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + } } static void sp_group_render(SPItem *item, CairoRenderContext *ctx) diff --git a/src/extension/internal/filter/filter.cpp b/src/extension/internal/filter/filter.cpp index 048207332..d98f8e9a2 100644 --- a/src/extension/internal/filter/filter.cpp +++ b/src/extension/internal/filter/filter.cpp @@ -70,7 +70,7 @@ Filter::get_filter (Inkscape::Extension::Extension * ext) { return sp_repr_read_mem(filter, strlen(filter), NULL); } -void +void Filter::merge_filters (Inkscape::XML::Node * to, Inkscape::XML::Node * from, Inkscape::XML::Document * doc, gchar * srcGraphic, gchar * srcGraphicAlpha) { if (from == NULL) return; @@ -99,7 +99,7 @@ Filter::merge_filters (Inkscape::XML::Node * to, Inkscape::XML::Node * from, Ink from_child != NULL ; from_child = from_child->next()) { Glib::ustring name = "svg:"; name += from_child->name(); - + Inkscape::XML::Node * to_child = doc->createElement(name.c_str()); to->appendChild(to_child); merge_filters(to_child, from_child, doc, srcGraphic, srcGraphicAlpha); @@ -149,7 +149,7 @@ Filter::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *d Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")"; merge_filters(newfilterroot, filterdoc->root(), xmldoc); - + Inkscape::GC::release(newfilterroot); sp_repr_css_set_property(css, "filter", url.c_str()); @@ -170,21 +170,29 @@ Filter::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *d } g_free(lfilter); + // no filter if (filternode == NULL) { + g_warning("no assoziating filter found!"); continue; } - filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC); + if (filternode->lastChild() == NULL) { + // empty filter, we insert + merge_filters(filternode, filterdoc->root(), xmldoc); + } else { + // existing filter, we merge + filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC); + Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix"); + alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA); + alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit + alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"); - Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix"); - alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA); - alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit - alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"); - filternode->appendChild(alpha); + filternode->appendChild(alpha); - merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA); + merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA); - Inkscape::GC::release(alpha); + Inkscape::GC::release(alpha); + } } } diff --git a/src/extension/internal/javafx-out.cpp b/src/extension/internal/javafx-out.cpp index 417755e19..a2f387406 100644 --- a/src/extension/internal/javafx-out.cpp +++ b/src/extension/internal/javafx-out.cpp @@ -141,7 +141,7 @@ static JavaFXOutput::String rgba(guint32 rgba) unsigned int a = SP_RGBA32_A_U(rgba); char buf[80]; snprintf(buf, 79, "Color.rgb(0x%02x, 0x%02x, 0x%02x, %s)", - r, g, b, DSTR((double)a/256.0)); + r, g, b, DSTR((double)a/255.0)); JavaFXOutput::String s = buf; return s; } diff --git a/src/extension/internal/pov-out.cpp b/src/extension/internal/pov-out.cpp index f30cbc317..1cb14fb58 100644 --- a/src/extension/internal/pov-out.cpp +++ b/src/extension/internal/pov-out.cpp @@ -303,14 +303,28 @@ bool PovOutput::doCurve(SPItem *item, const String &id) Geom::Matrix tf = sp_item_i2d_affine(item); Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf ); - //Count the NR_CURVETOs/LINETOs (including closing line segment) + /* + * We need to know the number of segments (NR_CURVETOs/LINETOs, including + * closing line segment) before we write out segment data. Since we are + * going to skip degenerate (zero length) paths, we need to loop over all + * subpaths and segments first. + */ int segmentCount = 0; - for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) - { - segmentCount += (*it).size(); - if (it->closed()) - segmentCount += 1; + /** + * For all Subpaths in the <path> + */ + for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) + { + /** + * For all segments in the subpath, including extra closing segment defined by 2geom + */ + for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) + { + + // Skip zero length segments. + if( !cit->isDegenerate() ) ++segmentCount; } + } out("/*###################################################\n"); out("### PRISM: %s\n", id.c_str()); @@ -340,10 +354,14 @@ bool PovOutput::doCurve(SPItem *item, const String &id) cminmax.expandTo(pit->initialPoint()); /** - * For all segments in the subpath + * For all segments in the subpath, including extra closing segment defined by 2geom */ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) - { + { + + // Skip zero length segments + if( cit->isDegenerate() ) + continue; if( is_straight_curve(*cit) ) { @@ -365,7 +383,7 @@ bool PovOutput::doCurve(SPItem *item, const String &id) nrNodes += 8; } else - { + { err("logical error, because pathv_to_linear_and_cubic_beziers was used"); return false; } @@ -374,6 +392,11 @@ bool PovOutput::doCurve(SPItem *item, const String &id) out(",\n"); else out("\n"); + if (segmentNr > segmentCount) + { + err("Too many segments"); + return false; + } cminmax.expandTo(cit->finalPoint()); diff --git a/src/extension/output.cpp b/src/extension/output.cpp index e1481d000..742e938de 100644 --- a/src/extension/output.cpp +++ b/src/extension/output.cpp @@ -218,7 +218,7 @@ Output::save(SPDocument *doc, gchar const *filename) imp->save(this, doc, filename); } catch (...) { - g_warning("There was an error saving the file."); + throw Inkscape::Extension::Output::save_failed(); } return; diff --git a/src/extension/output.h b/src/extension/output.h index b52a96211..584fafda8 100644 --- a/src/extension/output.h +++ b/src/extension/output.h @@ -31,6 +31,7 @@ public: class save_failed {}; /**< Generic failure for an undescribed reason */ class save_cancelled {}; /**< Saving was cancelled */ class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */ + class file_read_only {}; /**< The existing file can not be opened for writing */ Output (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp); diff --git a/src/extension/system.cpp b/src/extension/system.cpp index a7828d3fc..6ffa7f57f 100644 --- a/src/extension/system.cpp +++ b/src/extension/system.cpp @@ -20,6 +20,8 @@ #include <interface.h> +#include "system.h" +#include "preferences.h" #include "extension.h" #include "db.h" #include "input.h" @@ -30,6 +32,7 @@ #include "implementation/script.h" #include "implementation/xslt.h" #include "xml/rebase-hrefs.h" +#include "io/sys.h" /* #include "implementation/plugin.h" */ namespace Inkscape { @@ -184,7 +187,8 @@ open_internal(Extension *in_plug, gpointer in_data) * Lastly, the save function is called in the module itself. */ void -save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official) +save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method) { Output *omod; if (key == NULL) { @@ -202,7 +206,7 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, } /* If autodetect fails, save as Inkscape SVG */ if (omod == NULL) { - omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); + // omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); use exception and let user choose } } else { omod = dynamic_cast<Output *>(key); @@ -245,17 +249,25 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, throw Output::no_overwrite(); } + // test if the file exists and is writable + // the test only checks the file attributes and might pass where ACL does not allow to write + if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_is_writable(filename)) { + g_free(fileName); + throw Output::file_read_only(); + } + Inkscape::XML::Node *repr = sp_document_repr_root(doc); - // remember attributes in case this is an unofficial save + + // remember attributes in case this is an unofficial save and/or overwrite fails + gchar *saved_uri = g_strdup(doc->uri); bool saved_modified = false; gchar *saved_output_extension = NULL; gchar *saved_dataloss = NULL; - if (!official) { - saved_modified = doc->isModifiedSinceSave(); - saved_output_extension = g_strdup(repr->attribute("inkscape:output_extension")); - saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss")); - } else { + saved_modified = doc->isModifiedSinceSave(); + saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str()); + saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss")); + if (official) { /* The document is changing name/uri. */ sp_document_change_uri_and_hrefs(doc, fileName); } @@ -266,7 +278,7 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, sp_document_set_undo_sensitive(doc, false); { // also save the extension for next use - repr->setAttribute("inkscape:output_extension", omod->get_id()); + store_file_extension_in_prefs (omod->get_id(), save_method); // set the "dataloss" attribute if the chosen extension is lossy repr->setAttribute("inkscape:dataloss", NULL); if (omod->causes_dataloss()) { @@ -277,14 +289,38 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, doc->setModifiedSinceSave(false); } - omod->save(doc, fileName); + try { + omod->save(doc, fileName); + } + catch(...) { + // revert attributes in case of official and overwrite + if(check_overwrite && official) { + bool const saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, false); + { + store_file_extension_in_prefs (saved_output_extension, save_method); + repr->setAttribute("inkscape:dataloss", saved_dataloss); + } + sp_document_set_undo_sensitive(doc, saved); + sp_document_change_uri_and_hrefs(doc, saved_uri); + } + doc->setModifiedSinceSave(saved_modified); + // free used ressources + g_free(saved_output_extension); + g_free(saved_dataloss); + g_free(saved_uri); + + g_free(fileName); + + throw Inkscape::Extension::Output::save_failed(); + } // If it is an unofficial save, set the modified attributes back to what they were. if ( !official) { bool const saved = sp_document_get_undo_sensitive(doc); sp_document_set_undo_sensitive(doc, false); { - repr->setAttribute("inkscape:output_extension", saved_output_extension); + store_file_extension_in_prefs (saved_output_extension, save_method); repr->setAttribute("inkscape:dataloss", saved_dataloss); } sp_document_set_undo_sensitive(doc, saved); @@ -519,6 +555,106 @@ build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp) return ext; } +/* + * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for + * filename_extension in sp_file_save_dialog(). + */ +Glib::ustring +get_file_save_extension (Inkscape::Extension::FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring extension; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + extension = prefs->getString("/dialogs/save_as/default"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + extension = prefs->getString("/dialogs/save_copy/default"); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + break; + } + + if(extension.empty()) + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + + return extension; +} + +Glib::ustring +get_file_save_path (SPDocument *doc, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring path; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + { + bool use_current_dir = prefs->getBool("/dialogs/save_as/use_current_dir"); + if (doc->uri && use_current_dir) { + path = Glib::path_get_dirname(doc->uri); + } else { + path = prefs->getString("/dialogs/save_as/path"); + } + break; + } + case FILE_SAVE_METHOD_TEMPORARY: + path = prefs->getString("/dialogs/save_as/path"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + path = prefs->getString("/dialogs/save_copy/path"); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + if (doc->uri) { + path = Glib::path_get_dirname(doc->uri); + } else { + // FIXME: should we use the save_as path here or something else? Maybe we should + // leave this as a choice to the user. + path = prefs->getString("/dialogs/save_as/path"); + } + } + + if(path.empty()) + path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid + // g_get_current_dir because this leads to problems on OS X where + // Inkscape opens the dialog inside application bundle when it is + // invoked for the first teim. + + return path; +} + +void +store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/default", extension); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/default", extension); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + // do nothing + break; + } +} + +void +store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/path", path); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/path", path); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + // do nothing + break; + } +} } } /* namespace Inkscape::Extension */ diff --git a/src/extension/system.h b/src/extension/system.h index 6c23b2f0d..b6740e109 100644 --- a/src/extension/system.h +++ b/src/extension/system.h @@ -21,13 +21,64 @@ namespace Inkscape { namespace Extension { +/** + * Used to distinguish between the various invocations of the save dialogs (and thus to determine + * the file type and save path offered in the dialog) + */ +enum FileSaveMethod { + FILE_SAVE_METHOD_SAVE_AS, + FILE_SAVE_METHOD_SAVE_COPY, + FILE_SAVE_METHOD_EXPORT, + // Fallback for special cases (e.g., when saving a document for the first time or after saving + // it in a lossy format) + FILE_SAVE_METHOD_INKSCAPE_SVG, + // For saving temporary files; we return the same data as for FILE_SAVE_METHOD_SAVE_AS + FILE_SAVE_METHOD_TEMPORARY, +}; + SPDocument *open(Extension *key, gchar const *filename); void save(Extension *key, SPDocument *doc, gchar const *filename, - bool setextension, bool check_overwrite, bool official); + bool setextension, bool check_overwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method); Print *get_print(gchar const *key); Extension *build_from_file(gchar const *filename); Extension *build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp); +/** + * Determine the desired default file extension depending on the given file save method. + * The returned string is guaranteed to be non-empty. + * + * @param method the file save method of the dialog + * @return the corresponding default file extension + */ +Glib::ustring get_file_save_extension (FileSaveMethod method); + +/** + * Determine the desired default save path depending on the given FileSaveMethod. + * The returned string is guaranteed to be non-empty. + * + * @param method the file save method of the dialog + * @param doc the file's document + * @return the corresponding default save path + */ +Glib::ustring get_file_save_path (SPDocument *doc, FileSaveMethod method); + +/** + * Write the given file extension back to prefs so that it can be used later on. + * + * @param extension the file extension which should be written to prefs + * @param method the file save mathod of the dialog + */ +void store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method); + +/** + * Write the given path back to prefs so that it can be used later on. + * + * @param path the path which should be written to prefs + * @param method the file save mathod of the dialog + */ +void store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method); + } } /* namespace Inkscape::Extension */ #endif /* INKSCAPE_EXTENSION_SYSTEM_H__ */ diff --git a/src/file.cpp b/src/file.cpp index 7acd5fe5d..1186a1f07 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -60,7 +60,6 @@ #include "selection.h" #include "sp-namedview.h" #include "style.h" -#include "ui/dialog/filedialog.h" #include "ui/dialog/ocaldialogs.h" #include "ui/view/view-widget.h" #include "uri.h" @@ -70,9 +69,9 @@ # include <libgnomevfs/gnome-vfs.h> #endif -#ifdef WITH_INKBOARD -#include "jabber_whiteboard/session-manager.h" -#endif +//#ifdef WITH_INKBOARD +//#include "jabber_whiteboard/session-manager.h" +//#endif #ifdef WIN32 #include <windows.h> @@ -141,31 +140,48 @@ sp_file_new(const Glib::ustring &templ) return dt; } -SPDesktop* -sp_file_new_default() +SPDesktop* sp_file_new_default() { std::list<gchar *> sources; sources.push_back( profile_path("templates") ); // first try user's local dir sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir - - while (!sources.empty()) { - gchar *dirname = sources.front(); - if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { - - // TRANSLATORS: default.svg is localizable - this is the name of the default document - // template. This way you can localize the default pagesize, translate the name of - // the default layer, etc. If you wish to localize this file, please create a - // localized share/templates/default.xx.svg file, where xx is your language code. - char *default_template = g_build_filename(dirname, _("default.svg"), NULL); - if (Inkscape::IO::file_test(default_template, G_FILE_TEST_IS_REGULAR)) { - return sp_file_new(default_template); + std::list<gchar const*> baseNames; + gchar const* localized = _("default.svg"); + if (strcmp("default.svg", localized) != 0) { + baseNames.push_back(localized); + } + baseNames.push_back("default.svg"); + gchar *foundTemplate = 0; + + for (std::list<gchar const*>::iterator nameIt = baseNames.begin(); (nameIt != baseNames.end()) && !foundTemplate; ++nameIt) { + for (std::list<gchar *>::iterator it = sources.begin(); (it != sources.end()) && !foundTemplate; ++it) { + gchar *dirname = *it; + if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { + + // TRANSLATORS: default.svg is localizable - this is the name of the default document + // template. This way you can localize the default pagesize, translate the name of + // the default layer, etc. If you wish to localize this file, please create a + // localized share/templates/default.xx.svg file, where xx is your language code. + char *tmp = g_build_filename(dirname, *nameIt, NULL); + if (Inkscape::IO::file_test(tmp, G_FILE_TEST_IS_REGULAR)) { + foundTemplate = tmp; + } else { + g_free(tmp); + } } } - g_free(dirname); - sources.pop_front(); } - return sp_file_new(""); + for (std::list<gchar *>::iterator it = sources.begin(); it != sources.end(); ++it) { + g_free(*it); + } + + SPDesktop* desk = sp_file_new(foundTemplate ? foundTemplate : ""); + if (foundTemplate) { + g_free(foundTemplate); + foundTemplate = 0; + } + return desk; } @@ -575,15 +591,17 @@ sp_file_vacuum() */ static bool file_save(Gtk::Window &parentWindow, SPDocument *doc, const Glib::ustring &uri, - Inkscape::Extension::Extension *key, bool saveas, bool official) + Inkscape::Extension::Extension *key, bool checkoverwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method) { if (!doc || uri.size()<1) //Safety check return false; try { Inkscape::Extension::save(key, doc, uri.c_str(), - false, - saveas, official); + false, + checkoverwrite, official, + save_method); } catch (Inkscape::Extension::Output::no_extension_found &e) { gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str()); gchar *text = g_strdup_printf(_("No Inkscape extension found to save document (%s). This may have been caused by an unknown filename extension."), safeUri); @@ -592,6 +610,14 @@ file_save(Gtk::Window &parentWindow, SPDocument *doc, const Glib::ustring &uri, g_free(text); g_free(safeUri); return FALSE; + } catch (Inkscape::Extension::Output::file_read_only &e) { + gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str()); + gchar *text = g_strdup_printf(_("File %s is write protected. Please remove write protection and try again."), safeUri); + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved.")); + sp_ui_error_dialog(text); + g_free(text); + g_free(safeUri); + return FALSE; } catch (Inkscape::Extension::Output::save_failed &e) { gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str()); gchar *text = g_strdup_printf(_("File %s could not be saved."), safeUri); @@ -604,7 +630,10 @@ file_save(Gtk::Window &parentWindow, SPDocument *doc, const Glib::ustring &uri, SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved.")); return FALSE; } catch (Inkscape::Extension::Output::no_overwrite &e) { - return sp_file_save_dialog(parentWindow, doc); + return sp_file_save_dialog(parentWindow, doc, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + } catch (...) { + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Document not saved.")); + return FALSE; } SP_ACTIVE_DESKTOP->event_log->rememberFileSave(); @@ -700,55 +729,46 @@ file_save_remote(SPDocument */*doc*/, /** * Display a SaveAs dialog. Save the document if OK pressed. - * - * \param ascopy (optional) wether to set the documents->uri to the new filename or not */ bool -sp_file_save_dialog(Gtk::Window &parentWindow, SPDocument *doc, bool is_copy) +sp_file_save_dialog(Gtk::Window &parentWindow, SPDocument *doc, Inkscape::Extension::FileSaveMethod save_method) { - - Inkscape::XML::Node *repr = sp_document_repr_root(doc); Inkscape::Extension::Output *extension = 0; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool is_copy = (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY); - //# Get the default extension name + // Note: default_extension has the format "org.inkscape.output.svg.inkscape", whereas + // filename_extension only uses ".svg" Glib::ustring default_extension; - char *attr = (char *)repr->attribute("inkscape:output_extension"); - if (!attr) { - Glib::ustring attr2 = prefs->getString("/dialogs/save_as/default"); - if(!attr2.empty()) default_extension = attr2; - } else { - default_extension = attr; - } + Glib::ustring filename_extension = ".svg"; + + default_extension= Inkscape::Extension::get_file_save_extension(save_method); //g_message("%s: extension name: '%s'", __FUNCTION__, default_extension); + extension = dynamic_cast<Inkscape::Extension::Output *> + (Inkscape::Extension::db.get(default_extension.c_str())); + + if (extension) + filename_extension = extension->get_extension(); + Glib::ustring save_path; Glib::ustring save_loc; - if (doc->uri == NULL) { - char formatBuf[256]; - int i = 1; - - Glib::ustring filename_extension = ".svg"; - extension = dynamic_cast<Inkscape::Extension::Output *> - (Inkscape::Extension::db.get(default_extension.c_str())); - //g_warning("%s: extension ptr: 0x%x", __FUNCTION__, (unsigned int)extension); - if (extension) - filename_extension = extension->get_extension(); + save_path = Inkscape::Extension::get_file_save_path(doc, save_method); - Glib::ustring attr3 = prefs->getString("/dialogs/save_as/path"); - if (!attr3.empty()) - save_path = attr3; + if (!Inkscape::IO::file_test(save_path.c_str(), + (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + save_path = ""; - if (!Inkscape::IO::file_test(save_path.c_str(), - (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - save_path = ""; + if (save_path.size()<1) + save_path = g_get_home_dir(); - if (save_path.size()<1) - save_path = g_get_home_dir(); + save_loc = save_path; + save_loc.append(G_DIR_SEPARATOR_S); - save_loc = save_path; - save_loc.append(G_DIR_SEPARATOR_S); + char formatBuf[256]; + int i = 1; + if (!doc->uri) { + // We are saving for the first time; create a unique default filename snprintf(formatBuf, 255, _("drawing%s"), filename_extension.c_str()); save_loc.append(formatBuf); @@ -759,8 +779,8 @@ sp_file_save_dialog(Gtk::Window &parentWindow, SPDocument *doc, bool is_copy) save_loc.append(formatBuf); } } else { - save_loc = Glib::build_filename(Glib::path_get_dirname(doc->uri), - Glib::path_get_basename(doc->uri)); + snprintf(formatBuf, 255, _("%s"), Glib::path_get_basename(doc->uri).c_str()); + save_loc.append(formatBuf); } // convert save_loc from utf-8 to locale @@ -786,7 +806,8 @@ sp_file_save_dialog(Gtk::Window &parentWindow, SPDocument *doc, bool is_copy) Inkscape::UI::Dialog::SVG_TYPES, dialog_title, default_extension, - doc_title ? doc_title : "" + doc_title ? doc_title : "", + save_method ); saveDialog->setSelectionType(extension); @@ -817,15 +838,15 @@ sp_file_save_dialog(Gtk::Window &parentWindow, SPDocument *doc, bool is_copy) else g_warning( "Error converting save filename to UTF-8." ); - success = file_save(parentWindow, doc, fileName, selectionType, TRUE, !is_copy); + // FIXME: does the argument !is_copy really convey the correct meaning here? + success = file_save(parentWindow, doc, fileName, selectionType, TRUE, !is_copy, save_method); if (success && SP_DOCUMENT_URI(doc)) { sp_file_add_recent(SP_DOCUMENT_URI(doc)); } save_path = Glib::path_get_dirname(fileName); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setString("/dialogs/save_as/path", save_path); + Inkscape::Extension::store_save_path_in_prefs(save_path, save_method); return success; } @@ -844,16 +865,30 @@ sp_file_save_document(Gtk::Window &parentWindow, SPDocument *doc) bool success = true; if (doc->isModifiedSinceSave()) { - Inkscape::XML::Node *repr = sp_document_repr_root(doc); - if ( doc->uri == NULL - || repr->attribute("inkscape:output_extension") == NULL ) + if ( doc->uri == NULL ) { - return sp_file_save_dialog(parentWindow, doc, FALSE); + // Hier sollte in Argument mitgegeben werden, das anzeigt, da� das Dokument das erste + // Mal gespeichert wird, so da� als default .svg ausgew�hlt wird und nicht die zuletzt + // benutzte "Save as ..."-Endung + return sp_file_save_dialog(parentWindow, doc, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG); } else { - gchar const *fn = g_strdup(doc->uri); - gchar const *ext = repr->attribute("inkscape:output_extension"); - success = file_save(parentWindow, doc, fn, Inkscape::Extension::db.get(ext), FALSE, TRUE); - g_free((void *) fn); + Glib::ustring extension = Inkscape::Extension::get_file_save_extension(Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + Glib::ustring fn = g_strdup(doc->uri); + // Try to determine the extension from the uri; this may not lead to a valid extension, + // but this case is caught in the file_save method below (or rather in Extension::save() + // further down the line). + Glib::ustring ext = ""; + Glib::ustring::size_type pos = fn.rfind('.'); + if (pos != Glib::ustring::npos) { + // FIXME: this could/should be more sophisticated (see FileSaveDialog::appendExtension()), + // but hopefully it's a reasonable workaround for now + ext = fn.substr( pos ); + } + success = file_save(parentWindow, doc, fn, Inkscape::Extension::db.get(ext.c_str()), FALSE, TRUE, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + if (success == false) { + // give the user the chance to change filename or extension + return sp_file_save_dialog(parentWindow, doc, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG); + } } } else { SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No changes need to be saved.")); @@ -889,7 +924,7 @@ sp_file_save_as(Gtk::Window &parentWindow, gpointer /*object*/, gpointer /*data* if (!SP_ACTIVE_DOCUMENT) return false; sp_namedview_document_from_window(SP_ACTIVE_DESKTOP); - return sp_file_save_dialog(parentWindow, SP_ACTIVE_DOCUMENT, FALSE); + return sp_file_save_dialog(parentWindow, SP_ACTIVE_DOCUMENT, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); } @@ -903,7 +938,7 @@ sp_file_save_a_copy(Gtk::Window &parentWindow, gpointer /*object*/, gpointer /*d if (!SP_ACTIVE_DOCUMENT) return false; sp_namedview_document_from_window(SP_ACTIVE_DESKTOP); - return sp_file_save_dialog(parentWindow, SP_ACTIVE_DOCUMENT, TRUE); + return sp_file_save_dialog(parentWindow, SP_ACTIVE_DOCUMENT, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY); } @@ -1122,9 +1157,6 @@ sp_file_import(Gtk::Window &parentWindow) ## E X P O R T ######################*/ -//#define NEW_EXPORT_DIALOG - - #ifdef NEW_EXPORT_DIALOG @@ -1132,7 +1164,7 @@ sp_file_import(Gtk::Window &parentWindow) * Display an Export dialog, export as the selected type if OK pressed */ bool -sp_file_export_dialog(void *widget) +sp_file_export_dialog(Gtk::Window &parentWindow) { //# temp hack for 'doc' until we can switch to this dialog SPDocument *doc = SP_ACTIVE_DOCUMENT; @@ -1140,18 +1172,14 @@ sp_file_export_dialog(void *widget) Glib::ustring export_path; Glib::ustring export_loc; - Inkscape::XML::Node *repr = sp_document_repr_root(doc); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Inkscape::Extension::Output *extension; //# Get the default extension name - Glib::ustring default_extension; - char *attr = (char *)repr->attribute("inkscape:output_extension"); - if (!attr) { - Glib::ustring attr2 = prefs->getString("/dialogs/save_as/default"); - if(!attr2.empty()) default_extension = attr2; - } else { - default_extension = attr; + Glib::ustring default_extension = prefs->getString("/dialogs/save_export/default"); + if(default_extension.empty()) { + // FIXME: Is this a good default? Should there be a macro for the string? + default_extension = "org.inkscape.output.png.cairo"; } //g_message("%s: extension name: '%s'", __FUNCTION__, default_extension); @@ -1166,7 +1194,7 @@ sp_file_export_dialog(void *widget) if (extension) filename_extension = extension->get_extension(); - Glib::ustring attr3 = prefs->getString("/dialogs/save_as/path"); + Glib::ustring attr3 = prefs->getString("/dialogs/save_export/path"); if (!attr3.empty()) export_path = attr3; @@ -1195,9 +1223,10 @@ sp_file_export_dialog(void *widget) if ( export_path_local.size() > 0) export_path = export_path_local; - //# Show the SaveAs dialog + //# Show the Export dialog Inkscape::UI::Dialog::FileExportDialog *exportDialogInstance = Inkscape::UI::Dialog::FileExportDialog::create( + parentWindow, export_path, Inkscape::UI::Dialog::EXPORT_TYPES, (char const *) _("Select file to export to"), @@ -1226,7 +1255,7 @@ sp_file_export_dialog(void *widget) else g_warning( "Error converting save filename to UTF-8." ); - success = file_save(doc, fileName, selectionType, TRUE, FALSE); + success = file_save(parentWindow, doc, fileName, selectionType, TRUE, FALSE, Inkscape::Extension::FILE_SAVE_METHOD_EXPORT); if (success) { Glib::RefPtr<Gtk::RecentManager> recent = Gtk::RecentManager::get_default(); @@ -1234,7 +1263,7 @@ sp_file_export_dialog(void *widget) } export_path = fileName; - prefs->setString("/dialogs/save_as/path", export_path); + prefs->setString("/dialogs/save_export/path", export_path); return success; } @@ -1249,7 +1278,7 @@ sp_file_export_dialog(void *widget) * */ bool -sp_file_export_dialog(void */*widget*/) +sp_file_export_dialog(Gtk::Window &/*parentWindow*/) { sp_export_dialog(); return true; @@ -1264,6 +1293,7 @@ sp_file_export_dialog(void */*widget*/) /** * Display an Export dialog, export as the selected type if OK pressed */ +/* bool sp_file_export_to_ocal_dialog(Gtk::Window &parentWindow) { @@ -1343,7 +1373,7 @@ sp_file_export_to_ocal_dialog(Gtk::Window &parentWindow) fileName = filePath; - success = file_save(parentWindow, doc, filePath, selectionType, FALSE, FALSE); + success = file_save(parentWindow, doc, filePath, selectionType, FALSE, FALSE, Inkscape::Extension::FILE_SAVE_METHOD_EXPORT); if (!success){ gchar *text = g_strdup_printf(_("Error saving a temporary copy")); @@ -1400,10 +1430,11 @@ sp_file_export_to_ocal_dialog(Gtk::Window &parentWindow) return success; } - +*/ /** * Export the current document to OCAL */ +/* void sp_file_export_to_ocal(Gtk::Window &parentWindow) { @@ -1415,7 +1446,7 @@ sp_file_export_to_ocal(Gtk::Window &parentWindow) if (success) SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Document exported...")); } - +*/ /*###################### ## I M P O R T F R O M O C A L diff --git a/src/file.h b/src/file.h index ce75a61a7..97d1bd5f8 100644 --- a/src/file.h +++ b/src/file.h @@ -20,6 +20,7 @@ #include <gtk/gtkwidget.h> #include "extension/extension-forward.h" +#include "extension/system.h" struct SPDesktop; struct SPDocument; @@ -30,6 +31,7 @@ namespace Inkscape { } } + /*###################### ## N E W ######################*/ @@ -111,7 +113,7 @@ bool sp_file_save_a_copy (Gtk::Window &parentWindow, gpointer object, gpointer d bool sp_file_save_document (Gtk::Window &parentWindow, SPDocument *document); /* Do the saveas dialog with a document as the parameter */ -bool sp_file_save_dialog (Gtk::Window &parentWindow, SPDocument *doc, bool bAsCopy = FALSE); +bool sp_file_save_dialog (Gtk::Window &parentWindow, SPDocument *doc, Inkscape::Extension::FileSaveMethod save_method); /*###################### @@ -139,7 +141,7 @@ void file_import(SPDocument *in_doc, const Glib::ustring &uri, * additional type selection, to allow the user to export * the a document as a given type. */ -bool sp_file_export_dialog (void *widget); +bool sp_file_export_dialog (Gtk::Window &parentWindow); /*###################### @@ -149,13 +151,13 @@ bool sp_file_export_dialog (void *widget); /** * Export the current document to OCAL */ -void sp_file_export_to_ocal (Gtk::Window &parentWindow ); +//void sp_file_export_to_ocal (Gtk::Window &parentWindow ); /** * Export the current document to OCAL */ -bool sp_file_export_to_ocal_dialog (void *widget); +//bool sp_file_export_to_ocal_dialog (void *widget); /*###################### diff --git a/src/filters/componenttransfer.cpp b/src/filters/componenttransfer.cpp index 557d884e0..27e63eaa6 100644 --- a/src/filters/componenttransfer.cpp +++ b/src/filters/componenttransfer.cpp @@ -142,7 +142,6 @@ static void sp_feComponentTransfer_children_modified(SPFeComponentTransfer *sp_c static void sp_feComponentTransfer_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { - g_warning("child_added"); SPFeComponentTransfer *f = SP_FECOMPONENTTRANSFER(object); if (((SPObjectClass *) feComponentTransfer_parent_class)->child_added) diff --git a/src/filters/image.cpp b/src/filters/image.cpp index 0002ef94c..d8e5a417f 100644 --- a/src/filters/image.cpp +++ b/src/filters/image.cpp @@ -184,11 +184,12 @@ sp_feImage_set(SPObject *object, unsigned int key, gchar const *value) g_warning("SVG element URI was not found in the document while loading feImage"); } } - catch(const Inkscape::UnsupportedURIException & e) + // catches either MalformedURIException or UnsupportedURIException + catch(const Inkscape::BadURIException & e) { feImage->from_element = false; /* This occurs when using external image as the source */ - //g_warning("caught Inkscape::UnsupportedURIException in sp_feImage_set"); + //g_warning("caught Inkscape::BadURIException in sp_feImage_set"); break; } break; diff --git a/src/gradient-context.cpp b/src/gradient-context.cpp index b4eeda290..fc5c1af44 100644 --- a/src/gradient-context.cpp +++ b/src/gradient-context.cpp @@ -556,7 +556,6 @@ sp_gradient_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (!(event->button.state & GDK_CONTROL_MASK)) event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); - sp_event_context_snap_window_open(event_context, false); SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, button_dt, Inkscape::SNAPSOURCE_HANDLE); diff --git a/src/guide-snapper.cpp b/src/guide-snapper.cpp index 3a9a861e5..5cf97958a 100644 --- a/src/guide-snapper.cpp +++ b/src/guide-snapper.cpp @@ -26,9 +26,9 @@ Inkscape::GuideSnapper::GuideSnapper(SnapManager *sm, Geom::Coord const d) : Lin */ Geom::Coord Inkscape::GuideSnapper::getSnapperTolerance() const { - SPDesktop const *dt = _snapmanager->getDesktop(); - double const zoom = dt ? dt->current_zoom() : 1; - return _snapmanager->snapprefs.getGuideTolerance() / zoom; + SPDesktop const *dt = _snapmanager->getDesktop(); + double const zoom = dt ? dt->current_zoom() : 1; + return _snapmanager->snapprefs.getGuideTolerance() / zoom; } bool Inkscape::GuideSnapper::getSnapperAlwaysSnap() const @@ -49,7 +49,7 @@ Inkscape::GuideSnapper::LineList Inkscape::GuideSnapper::_getSnapLines(Geom::Poi for (GSList const *l = _snapmanager->getNamedView()->guides; l != NULL; l = l->next) { SPGuide const *g = SP_GUIDE(l->data); if (g != guide_to_ignore) { - s.push_back(std::make_pair(g->normal_to_line, g->point_on_line)); + s.push_back(std::make_pair(g->normal_to_line, g->point_on_line)); } } @@ -61,11 +61,11 @@ Inkscape::GuideSnapper::LineList Inkscape::GuideSnapper::_getSnapLines(Geom::Poi */ bool Inkscape::GuideSnapper::ThisSnapperMightSnap() const { - if (_snapmanager->getNamedView() == NULL) { - return false; - } + if (_snapmanager->getNamedView() == NULL) { + return false; + } - return (_snap_enabled && _snapmanager->snapprefs.getSnapToGuides() && _snapmanager->getNamedView()->showguides); + 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 @@ -74,10 +74,17 @@ void Inkscape::GuideSnapper::_addSnappedLine(SnappedConstraints &sc, Geom::Point 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 +{ + SnappedPoint dummy = SnappedPoint(origin, source, 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 { - SnappedPoint dummy = SnappedPoint(snapped_point, source, Inkscape::SNAPTARGET_GUIDE, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), true); - sc.points.push_back(dummy); + SnappedPoint dummy = SnappedPoint(snapped_point, source, 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 f9f433bf4..1dc602f72 100644 --- a/src/guide-snapper.h +++ b/src/guide-snapper.h @@ -35,6 +35,7 @@ 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, 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; }; diff --git a/src/helper/action.h b/src/helper/action.h index 4c99e31d8..c4367df62 100644 --- a/src/helper/action.h +++ b/src/helper/action.h @@ -26,7 +26,7 @@ #include "forward.h" #include <glibmm/ustring.h> -//class Inkscape::UI::View::View; +//class Inkscape::UI::View::View; namespace Inkscape { class Verb; diff --git a/src/helper/pixbuf-ops.cpp b/src/helper/pixbuf-ops.cpp index f41342e42..1e43df5f3 100644 --- a/src/helper/pixbuf-ops.cpp +++ b/src/helper/pixbuf-ops.cpp @@ -98,7 +98,6 @@ sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, { - GdkPixbuf* pixbuf = NULL; /* Create new arena for offscreen rendering*/ NRArena *arena = NRArena::create(); @@ -141,7 +140,12 @@ sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, nr_arena_item_invoke_update(root, &final_bbox, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE); - guchar *px = g_try_new(guchar, 4L * width * height); + guchar *px = NULL; + guint64 size = 4L * (guint64)width * (guint64)height; + if(size < (guint64)G_MAXSIZE) { + // g_try_new is limited to g_size type which is defined as unisgned int. Need to test for very large nubers + px = g_try_new(guchar, size); + } if(px != NULL) { @@ -158,15 +162,15 @@ sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, dtc[2] = NR_RGBA32_B(bgcolor); dtc[3] = NR_RGBA32_A(bgcolor); - for (unsigned int fy = 0; fy < height; fy++) { - guchar *p = NR_PIXBLOCK_PX(&B) + fy * B.rs; + for (gsize fy = 0; fy < height; fy++) { + guchar *p = NR_PIXBLOCK_PX(&B) + fy * (gsize)B.rs; for (unsigned int fx = 0; fx < width; fx++) { for (int i = 0; i < 4; i++) { *p++ = dtc[i]; } } } - + nr_arena_item_invoke_render(NULL, root, &final_bbox, &B, NR_ARENA_ITEM_RENDER_NO_CACHE ); @@ -178,7 +182,7 @@ sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, } else { - g_warning("sp_generate_internal_bitmap: not enough memory to create pixel buffer. Need %ld.", 4L * width * height); + g_warning("sp_generate_internal_bitmap: not enough memory to create pixel buffer. Need %lld.", size); } sp_item_invoke_hide (SP_ITEM(sp_document_root(doc)), dkey); nr_object_unref((NRObject *) arena); diff --git a/src/helper/png-write.cpp b/src/helper/png-write.cpp index 3ac900680..b1c135db0 100644 --- a/src/helper/png-write.cpp +++ b/src/helper/png-write.cpp @@ -414,20 +414,7 @@ sp_export_png_file(SPDocument *doc, gchar const *filename, g_return_val_if_fail(height >= 1, false); g_return_val_if_fail(!area.hasZeroArea(), false); - //Make relative paths absolute, if possible: - gchar *path = 0; - if (!g_path_is_absolute(filename) && doc->uri) { - gchar *dirname = g_path_get_dirname(doc->uri); - if (dirname) { - path = g_build_filename(dirname, filename, NULL); - g_free(dirname); - } - } - if (!path) { - path = g_strdup(filename); - } - - if (!force_overwrite && !sp_ui_overwrite_file(path)) { + if (!force_overwrite && !sp_ui_overwrite_file(filename)) { /* Remark: We return true so as not to invoke an error dialog in case export is cancelled by the user; currently this is safe because the callers only act when false is returned. If this changes in the future we need better distinction of return types (e.g., use int) @@ -493,12 +480,12 @@ sp_export_png_file(SPDocument *doc, gchar const *filename, if ((width < 256) || ((width * height) < 32768)) { ebp.px = nr_pixelstore_64K_new(FALSE, 0); ebp.sheight = 65536 / (4 * width); - write_status = sp_png_write_rgba_striped(doc, path, width, height, xdpi, ydpi, sp_export_get_rows, &ebp); + write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp); nr_pixelstore_64K_free(ebp.px); } else { ebp.sheight = 64; ebp.px = g_try_new(guchar, 4 * ebp.sheight * width); - write_status = sp_png_write_rgba_striped(doc, path, width, height, xdpi, ydpi, sp_export_get_rows, &ebp); + write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp); g_free(ebp.px); } @@ -508,8 +495,6 @@ sp_export_png_file(SPDocument *doc, gchar const *filename, /* Free arena */ nr_object_unref((NRObject *) arena); - g_free(path); - return write_status; } diff --git a/src/inkscape.cpp b/src/inkscape.cpp index 60ab895ed..8506f05de 100644 --- a/src/inkscape.cpp +++ b/src/inkscape.cpp @@ -634,6 +634,7 @@ inkscape_crash_handler (int /*signum*/) gchar * location = homedir_path(c); Inkscape::IO::dump_fopen_call(location, "E"); file = Inkscape::IO::fopen_utf8name(location, "w"); + g_snprintf (c, 1024, "%s", location); // we want the complete path to be stored in c (for reporting purposes) g_free(location); if (!file) { // try saving to /tmp @@ -643,9 +644,14 @@ inkscape_crash_handler (int /*signum*/) } if (!file) { // try saving to the current directory + gchar *curdir = g_get_current_dir(); g_snprintf (c, 1024, "inkscape-%.256s.%s.%d.svg", docname, sptstr, count); Inkscape::IO::dump_fopen_call(c, "F"); file = Inkscape::IO::fopen_utf8name(c, "w"); + // store the complete path in c so that it can be reported later + gchar * location = g_build_filename(curdir, c, NULL); + g_snprintf (c, 1024, "%s", location); + g_free(location); } if (file) { sp_repr_save_stream (repr->document(), file, SP_SVG_NS_URI); @@ -799,8 +805,10 @@ inkscape_application_init (const gchar *argv0, gboolean use_gui) } } - inkscape_load_menus(inkscape); - sp_input_load_from_preferences(); + if (use_gui) { + inkscape_load_menus(inkscape); + sp_input_load_from_preferences(); + } /* set language for user interface according setting in preferences */ Glib::ustring ui_language = prefs->getString("/ui/language"); @@ -1449,6 +1457,7 @@ profile_path(const char *filename) if (needsMigration) { // TODO here is a point to hook in preference migration g_warning("Preferences need to be migrated from 0.46 or older %s to %s", legacyDir, prefdir); + Inkscape::Preferences::migrate( legacyDir, prefdir ); } bool needsRenameWarning = ( !Inkscape::IO::file_test( prefdir, G_FILE_TEST_EXISTS ) && Inkscape::IO::file_test( dev47Dir, G_FILE_TEST_EXISTS ) ); @@ -1474,6 +1483,13 @@ profile_path(const char *filename) if ( g_mkdir_with_parents(prefdir, mode) == -1 ) { int problem = errno; g_warning("Unable to create profile directory (%s) (%d)", g_strerror(problem), problem); + } else { + gchar const *userDirs[] = {"keys", "templates", "icons", "extensions", "palettes", NULL}; + for (gchar const** name = userDirs; *name; ++name) { + gchar *dir = g_build_filename(prefdir, *name, NULL); + g_mkdir_with_parents(dir, mode); + g_free(dir); + } } } } diff --git a/src/interface.cpp b/src/interface.cpp index a751608c5..cf7072064 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -98,6 +98,7 @@ static GtkTargetEntry ui_drop_target_entries [] = { static GtkTargetEntry *completeDropTargets = 0; static int completeDropTargetsCount = 0; +static bool temporarily_block_actions = false; #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0]) static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries); @@ -364,7 +365,9 @@ sp_ui_close_all(void) static void sp_ui_menu_activate(void */*object*/, SPAction *action) { - sp_action_perform(action, NULL); + if (!temporarily_block_actions) { + sp_action_perform(action, NULL); + } } static void @@ -615,9 +618,8 @@ sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape:: sp_ui_menuitem_add_icon(item, action->image); } gtk_widget_set_events(item, GDK_KEY_PRESS_MASK); - g_signal_connect( G_OBJECT(item), "activate", - G_CALLBACK(sp_ui_menu_activate), action ); - + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action ); g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action ); g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); } @@ -680,6 +682,44 @@ checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_dat return FALSE; } +/** + * \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline) + */ + +static gboolean +update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data) +{ + SPAction *action = (SPAction *) user_data; + g_assert(action->id != NULL); + + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view"); + SPDesktop *dt = static_cast<SPDesktop*>(view); + Inkscape::RenderMode mode = dt->getMode(); + + bool new_state = false; + if (!strcmp(action->id, "ViewModeNormal")) { + new_state = mode == Inkscape::RENDERMODE_NORMAL; + } else if (!strcmp(action->id, "ViewModeNoFilters")) { + new_state = mode == Inkscape::RENDERMODE_NO_FILTERS; + } else if (!strcmp(action->id, "ViewModeOutline")) { + new_state = mode == Inkscape::RENDERMODE_OUTLINE; + } else { + g_warning("update_view_menu does not handle this verb"); + } + + if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { + // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second + // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled. + // This emission however should not invoke any actions, hence we block it here: + temporarily_block_actions = true; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE); + temporarily_block_actions = false; + } + } + + return FALSE; +} void sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref, @@ -870,7 +910,7 @@ private: a couple of submenus, it is unlikely this will go more than two or three times. - In the case of an unreconginzed verb, a menu item is made to identify + In the case of an unrecognized verb, a menu item is made to identify the verb that is missing, and display that. The menu item is also made insensitive. */ @@ -903,6 +943,10 @@ sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI: if (menu_pntr->attribute("default") != NULL) { gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); } + if (verb->get_code() != SP_VERB_NONE) { + SPAction *action = verb->get_action(view); + g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action); + } } else { sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view); group = NULL; diff --git a/src/io/sys.cpp b/src/io/sys.cpp index a5158c587..2841f0af8 100644 --- a/src/io/sys.cpp +++ b/src/io/sys.cpp @@ -15,6 +15,8 @@ # include "config.h" #endif +#include <glib.h> +#include <glib/gstdio.h> #include <glib/gutils.h> #include <glibmm/fileutils.h> #if GLIB_CHECK_VERSION(2,6,0) @@ -269,6 +271,38 @@ bool Inkscape::IO::file_test( char const *utf8name, GFileTest test ) return exists; } +bool Inkscape::IO::file_is_writable( char const *utf8name) +{ + bool success = true; + + if ( utf8name) { + gchar *filename = NULL; + if (utf8name && !g_utf8_validate(utf8name, -1, NULL)) { + /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable. + If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to + use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false) + to beginning of this function. */ + filename = g_strdup(utf8name); + // Looks like g_get_home_dir isn't safe. + //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!"); + } else { + filename = g_filename_from_utf8 ( utf8name, -1, NULL, NULL, NULL ); + } + if ( filename ) { + struct stat st; + if(g_lstat (filename, &st) == 0) { + success = ((st.st_mode & S_IWRITE) != 0); + } + g_free(filename); + filename = NULL; + } else { + g_warning( "Unable to convert filename in IO:file_test" ); + } + } + + return success; +} + /** Wrapper around g_dir_open, but taking a utf8name as first argument. */ GDir * Inkscape::IO::dir_open(gchar const *const utf8name, guint const flags, GError **const error) diff --git a/src/io/sys.h b/src/io/sys.h index 29c33c1c7..8623f6be9 100644 --- a/src/io/sys.h +++ b/src/io/sys.h @@ -38,6 +38,8 @@ int file_open_tmp( std::string& name_used, const std::string& prefix ); bool file_test( char const *utf8name, GFileTest test ); +bool file_is_writable( char const *utf8name); + GDir *dir_open(gchar const *utf8name, guint flags, GError **error); gchar *dir_read_utf8name(GDir *dir); diff --git a/src/io/uristream.cpp b/src/io/uristream.cpp index f529c7f8b..05d7f020a 100644 --- a/src/io/uristream.cpp +++ b/src/io/uristream.cpp @@ -429,7 +429,10 @@ void UriOutputStream::put(int ch) throw(StreamException) if (!outf) return; uch = (unsigned char)(ch & 0xff); - fputc(uch, outf); + if (fputc(uch, outf) == EOF) { + Glib::ustring err = "ERROR writing to file "; + throw StreamException(err); + } //fwrite(uch, 1, 1, outf); break; diff --git a/src/knot-holder-entity.cpp b/src/knot-holder-entity.cpp index 4225dd9e3..bf7505f3c 100644 --- a/src/knot-holder-entity.cpp +++ b/src/knot-holder-entity.cpp @@ -91,9 +91,12 @@ KnotHolderEntity::snap_knot_position(Geom::Point const &p) { Geom::Matrix const i2d (sp_item_i2d_affine(item)); Geom::Point s = p * i2d; + SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, item); + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE); + return s * i2d.inverse(); } @@ -102,10 +105,28 @@ KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape: { Geom::Matrix const i2d (sp_item_i2d_affine(item)); Geom::Point s = p * i2d; - Inkscape::Snapper::ConstraintLine transformed_constraint = Inkscape::Snapper::ConstraintLine(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); + SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, item); - m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if ((prefs->getBool("/options/snapmousepointer/value", false))) { // legacy behavior (pre v0.47) + // Snapping the mouse pointer instead of the constrained position of the knot allows to snap to + // things which don't intersect with the constraint line. This should be handled by the + // smart dynamic guides which are yet to be implemented, making this behavior more clean and + // transparent. With the current implementation it leads to unexpected results, and it doesn't + // allow accurately controlling what is being snapped to. + + // freeSnap() will try snapping point p. This will not take into account the constraint, which + // is therefore to be enforced after snap_knot_position_constrained() has finished + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE); + } else { + // constrainedSnap() will first project the point p onto the constraint line and then try to snap along that line. + // This way the constraint is already enforced, no need to worry about that later on + Inkscape::Snapper::ConstraintLine transformed_constraint = Inkscape::Snapper::ConstraintLine(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); + m.constrainedSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, s, Inkscape::SNAPSOURCE_HANDLE, transformed_constraint); + } + return s * i2d.inverse(); } diff --git a/src/knot.cpp b/src/knot.cpp index 1ac5d887b..b17e41b24 100644 --- a/src/knot.cpp +++ b/src/knot.cpp @@ -284,8 +284,6 @@ void sp_knot_start_dragging(SPKnot *knot, Geom::Point const &p, gint x, gint y, */ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot) { - static bool snap_delay_temporarily_active = false; - g_assert(knot != NULL); g_assert(SP_IS_KNOT(knot)); @@ -317,11 +315,7 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot if (event->button.button == 1 && !knot->desktop->event_context->space_panning) { Geom::Point const p = knot->desktop->w2d(Geom::Point(event->button.x, event->button.y)); sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time); - if (knot->desktop->event_context->_snap_window_open == false) { - sp_event_context_snap_window_open(knot->desktop->event_context); - snap_delay_temporarily_active = true; - } - consumed = TRUE; + consumed = TRUE; } break; case GDK_BUTTON_RELEASE: @@ -331,13 +325,7 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot sp_event_context_snap_watchdog_callback(knot->desktop->event_context->_delayed_snap_event); } - // now we can safely close the snapping window - if (snap_delay_temporarily_active) { - if (knot->desktop->event_context->_snap_window_open == true) { - sp_event_context_snap_window_closed(knot->desktop->event_context); - } - snap_delay_temporarily_active = false; - } + sp_event_context_discard_delayed_snap_event(knot->desktop->event_context); knot->pressure = 0; if (transform_escaped) { @@ -446,10 +434,7 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot } grabbed = FALSE; moved = FALSE; - if (snap_delay_temporarily_active) { - sp_event_context_snap_window_closed(knot->desktop->event_context); - snap_delay_temporarily_active = false; - } + sp_event_context_discard_delayed_snap_event(knot->desktop->event_context); break; default: consumed = FALSE; diff --git a/src/knotholder.cpp b/src/knotholder.cpp index 773b9249c..45cb140c0 100644 --- a/src/knotholder.cpp +++ b/src/knotholder.cpp @@ -30,6 +30,7 @@ #include "sp-pattern.h" #include "style.h" #include "live_effects/lpeobject.h" +#include "live_effects/effect.h" #include "desktop.h" #include "display/sp-canvas.h" @@ -139,7 +140,6 @@ KnotHolder::knot_moved_handler(SPKnot *knot, Geom::Point const &p, guint state) { if (this->dragging == false) { this->dragging = true; - //sp_event_context_snap_window_open(desktop->canvas); } // this was a local change and the knotholder does not need to be recreated: @@ -165,7 +165,6 @@ void KnotHolder::knot_ungrabbed_handler(SPKnot */*knot*/) { this->dragging = false; - //sp_event_context_snap_window_closed(desktop->canvas); if (this->released) { this->released(this->item); @@ -236,7 +235,7 @@ KnotHolder::add_pattern_knotholder() _("<b>Move</b> the pattern fill inside the object"), SP_KNOT_SHAPE_CROSS); entity_scale->create(desktop, item, this, - _("<b>Scale</b> the pattern fill uniformly"), + _("<b>Scale</b> the pattern fill; uniformly if with <b>Ctrl</b>"), SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); entity_angle->create(desktop, item, this, _("<b>Rotate</b> the pattern fill; with <b>Ctrl</b> to snap angle"), diff --git a/src/libgdl/gdl-switcher.c b/src/libgdl/gdl-switcher.c index 23f05b924..28b9fe661 100644 --- a/src/libgdl/gdl-switcher.c +++ b/src/libgdl/gdl-switcher.c @@ -42,7 +42,6 @@ #if HAVE_GNOME #include <gconf/gconf-client.h> -#include <libgnome/gnome-gconf.h> #endif static void gdl_switcher_set_property (GObject *object, diff --git a/src/libnr/Makefile_insert b/src/libnr/Makefile_insert index 0b90236b8..5cd2717be 100644 --- a/src/libnr/Makefile_insert +++ b/src/libnr/Makefile_insert @@ -15,6 +15,7 @@ ink_common_sources += \ libnr/nr-blit.h \ libnr/nr-compose-transform.cpp \ libnr/nr-compose-transform.h \ + libnr/nr-compose-reference.h \ libnr/nr-compose.cpp \ libnr/nr-compose.h \ libnr/nr-convert2geom.h \ diff --git a/src/libnr/nr-macros.h b/src/libnr/nr-macros.h index 0e0307916..37a3675e6 100644 --- a/src/libnr/nr-macros.h +++ b/src/libnr/nr-macros.h @@ -30,19 +30,20 @@ #define MIN(a,b) (((a) > (b)) ? (b) : (a)) #endif -#ifndef CLAMP /** Returns v bounded to within [a, b]. If v is NaN then returns a. * * \pre \a a \<= \a b. */ -# define CLAMP(v,a,b) \ +#define NR_CLAMP(v,a,b) \ (assert (a <= b), \ ((v) >= (a)) \ ? (((v) > (b)) \ ? (b) \ : (v)) \ : (a)) -#endif + +#undef CLAMP /* get rid of glib's version, which doesn't handle NaN correctly */ +#define CLAMP(v,a,b) NR_CLAMP(v,a,b) #define NR_DF_TEST_CLOSE(a,b,e) (fabs ((a) - (b)) <= (e)) diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp index 71387ac55..fec9316b9 100644 --- a/src/libnrtype/FontFactory.cpp +++ b/src/libnrtype/FontFactory.cpp @@ -81,7 +81,7 @@ ink_strstr(char const *haystack, char const *pneedle) char *needle, *q, *foundto; if (!*pneedle) return true; if (!haystack) return false; - + needle = buf; p = pneedle; q = needle; while ((*q++ = tolower(*p++))) @@ -202,7 +202,7 @@ is_swash(char const *s) * Determines if two style names match. This allows us to match * based on the type of style rather than simply doing string matching, * because for instance 'Plain' and 'Normal' mean the same thing. - * + * * Q: Shouldn't this include the other tests such as is_outline, etc.? * Q: Is there a problem with strcasecmp on Win32? Should it use stricmp? */ @@ -211,22 +211,22 @@ style_name_compare(char const *aa, char const *bb) { char const *a = (char const *) aa; char const *b = (char const *) bb; - + if (is_regular(a) && !is_regular(b)) return -1; if (is_regular(b) && !is_regular(a)) return 1; - + if (is_bold(a) && !is_bold(b)) return 1; if (is_bold(b) && !is_bold(a)) return -1; - + if (is_italic(a) && !is_italic(b)) return 1; if (is_italic(b) && !is_italic(a)) return -1; - + if (is_nonbold(a) && !is_nonbold(b)) return 1; if (is_nonbold(b) && !is_nonbold(a)) return -1; - + if (is_caps(a) && !is_caps(b)) return 1; if (is_caps(b) && !is_caps(a)) return -1; - + return strcasecmp(a, b); } @@ -238,18 +238,18 @@ style_record_compare(void const *aa, void const *bb) { NRStyleRecord const *a = (NRStyleRecord const *) aa; NRStyleRecord const *b = (NRStyleRecord const *) bb; - + return (style_name_compare(a->name, b->name)); } -static void font_factory_name_list_destructor(NRNameList *list) +static void font_factory_name_list_destructor(NRNameList *list) { - for (unsigned int i = 0; i < list->length; i++) + for (unsigned int i = 0; i < list->length; i++) g_free(list->names[i]); if ( list->names ) g_free(list->names); } -static void font_factory_style_list_destructor(NRStyleList *list) +static void font_factory_style_list_destructor(NRStyleList *list) { for (unsigned int i = 0; i < list->length; i++) { g_free((void *) (list->records)[i].name); @@ -331,7 +331,7 @@ font_factory::~font_factory(void) //pango_ft2_shutdown_display(); #endif //g_object_unref(fontContext); - + // Delete the pango font pointers in the string to instance map PangoStringToDescrMap::iterator it = fontInstanceMap.begin(); while (it != fontInstanceMap.end()) { @@ -344,116 +344,116 @@ font_factory::~font_factory(void) Glib::ustring font_factory::ConstructFontSpecification(PangoFontDescription *font) { Glib::ustring pangoString; - + g_assert(font); - + if (font) { // Once the format for the font specification is decided, it must be // kept.. if it is absolutely necessary to change it, the attribute // it is written to needs to have a new version so the legacy files // can be read. - + PangoFontDescription *copy = pango_font_description_copy(font); - + pango_font_description_unset_fields (copy, PANGO_FONT_MASK_SIZE); pangoString = Glib::ustring(pango_font_description_to_string(copy)); - + pango_font_description_free(copy); - + } - + return pangoString; } Glib::ustring font_factory::ConstructFontSpecification(font_instance *font) { Glib::ustring pangoString; - + g_assert(font); - + if (font) { pangoString = ConstructFontSpecification(font->descr); } - + return pangoString; } Glib::ustring font_factory::GetUIFamilyString(PangoFontDescription const *fontDescr) { Glib::ustring family; - + g_assert(fontDescr); - + if (fontDescr) { // For now, keep it as family name taken from pango family = pango_font_description_get_family(fontDescr); } - + return family; } Glib::ustring font_factory::GetUIStyleString(PangoFontDescription const *fontDescr) { Glib::ustring style; - + g_assert(fontDescr); - + if (fontDescr) { PangoFontDescription *fontDescrCopy = pango_font_description_copy(fontDescr); - + pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_FAMILY); pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_SIZE); - + // For now, keep it as style name taken from pango style = pango_font_description_to_string(fontDescrCopy); - + pango_font_description_free(fontDescrCopy); } - - return style; + + return style; } Glib::ustring font_factory::ReplaceFontSpecificationFamily(const Glib::ustring & fontSpec, const Glib::ustring & newFamily) { Glib::ustring newFontSpec; - + // Although we are using the string from pango_font_description_to_string for the // font specification, we definitely cannot just set the new family in the // PangoFontDescription structure and ask for a new string. This is because // what constitutes a "family" in our own UI may be different from how Pango // sees it. - + // Find the PangoFontDescription associated to this fontSpec PangoStringToDescrMap::iterator it = fontInstanceMap.find(fontSpec); - + if (it != fontInstanceMap.end()) { PangoFontDescription *descr = pango_font_description_copy((*it).second); - + // Grab the UI Family string from the descr Glib::ustring uiFamily = GetUIFamilyString(descr); - + // Replace the UI Family name with the new family name std::size_t found = fontSpec.find(uiFamily); if (found != Glib::ustring::npos) { newFontSpec = fontSpec; newFontSpec.erase(found, uiFamily.size()); newFontSpec.insert(found, newFamily); - + // If the new font specification does not exist in the reference maps, - // search for the next best match for the faces in that style + // search for the next best match for the faces in that style it = fontInstanceMap.find(newFontSpec); if (it == fontInstanceMap.end()) { - + PangoFontDescription *newFontDescr = pango_font_description_from_string(newFontSpec.c_str()); - + PangoFontDescription *bestMatchForNewDescr = NULL; Glib::ustring bestMatchFontDescription; - + bool setFirstFamilyMatch = false; for (it = fontInstanceMap.begin(); it != fontInstanceMap.end(); it++) { - + Glib::ustring currentFontSpec = (*it).first; - + // Save some time by only looking at the right family if (currentFontSpec.find(newFamily) != Glib::ustring::npos) { if (!setFirstFamilyMatch) { @@ -466,10 +466,10 @@ Glib::ustring font_factory::ReplaceFontSpecificationFamily(const Glib::ustring & // Get the font description that corresponds, and // then see if we've found a better match PangoFontDescription *possibleMatch = pango_font_description_copy((*it).second); - + if (pango_font_description_better_match( newFontDescr, bestMatchForNewDescr, possibleMatch)) { - + pango_font_description_free(bestMatchForNewDescr); bestMatchForNewDescr = possibleMatch; bestMatchFontDescription = currentFontSpec; @@ -479,31 +479,37 @@ Glib::ustring font_factory::ReplaceFontSpecificationFamily(const Glib::ustring & } } } - + newFontSpec = bestMatchFontDescription; - + pango_font_description_free(newFontDescr); pango_font_description_free(bestMatchForNewDescr); } } - + pango_font_description_free(descr); } - + return newFontSpec; } +/** + apply style property to the given font + @param fontSpec the given font + @param turnOn true to set italic style + @return the changed fontspec, if the property can not be set return an empty string +*/ Glib::ustring font_factory::FontSpecificationSetItalic(const Glib::ustring & fontSpec, bool turnOn) { Glib::ustring newFontSpec; - + // Find the PangoFontDesecription that goes with this font specification string PangoStringToDescrMap::iterator it = fontInstanceMap.find(fontSpec); - + if (it != fontInstanceMap.end()) { // If we did find one, make a copy and set/unset the italic as needed PangoFontDescription *descr = pango_font_description_copy((*it).second); - + PangoStyle style; if (turnOn) { style = PANGO_STYLE_ITALIC; @@ -511,31 +517,51 @@ Glib::ustring font_factory::FontSpecificationSetItalic(const Glib::ustring & fon style = PANGO_STYLE_NORMAL; } pango_font_description_set_style(descr, style); - + newFontSpec = ConstructFontSpecification(descr); if (fontInstanceMap.find(newFontSpec) == fontInstanceMap.end()) { - // If the new font does not have an italic face, don't - // allow italics to be set! - newFontSpec = fontSpec; + if(turnOn) { + // there is no PANGO_STYLE_ITALIC let's test for PANGO_STYLE_OBLIQUE + style = PANGO_STYLE_OBLIQUE; + pango_font_description_set_style(descr, style); + + newFontSpec = ConstructFontSpecification(descr); + if (fontInstanceMap.find(newFontSpec) == fontInstanceMap.end()) { + // If the new font does not have even an oblique face, don't + // allow italics to be set! + newFontSpec = Glib::ustring(""); + } + + } else { + // If the new font does not have an italic face, don't + // allow italics to be set! + newFontSpec = Glib::ustring(""); + } } - + pango_font_description_free(descr); } - - return newFontSpec; + + return newFontSpec; } +/** + apply width property to the given font + @param fontSpec the given font + @param turnOn true to set bold + @return the changed fontspec, if the property can not be set return an empty string +*/ Glib::ustring font_factory::FontSpecificationSetBold(const Glib::ustring & fontSpec, bool turnOn) { Glib::ustring newFontSpec; - + // Find the PangoFontDesecription that goes with this font specification string PangoStringToDescrMap::iterator it = fontInstanceMap.find(fontSpec); - + if (it != fontInstanceMap.end()) { // If we did find one, make a copy and set/unset the bold as needed PangoFontDescription *descr = pango_font_description_copy((*it).second); - + PangoWeight weight; if (turnOn) { weight = PANGO_WEIGHT_BOLD; @@ -543,18 +569,18 @@ Glib::ustring font_factory::FontSpecificationSetBold(const Glib::ustring & fontS weight = PANGO_WEIGHT_NORMAL; } pango_font_description_set_weight(descr, weight); - + newFontSpec = ConstructFontSpecification(descr); if (fontInstanceMap.find(newFontSpec) == fontInstanceMap.end()) { // If the new font does not have a bold face, don't // allow bold to be set! - newFontSpec = fontSpec; + newFontSpec = Glib::ustring(""); } - + pango_font_description_free(descr); } - - return newFontSpec; + + return newFontSpec; } ///// @@ -567,51 +593,51 @@ static bool StyleNameCompareInternal(Glib::ustring style1, Glib::ustring style2) void font_factory::GetUIFamiliesAndStyles(FamilyToStylesMap *map) { g_assert(map); - + if (map) { - + // Gather the family names as listed by Pango PangoFontFamily** families = NULL; int numFamilies = 0; pango_font_map_list_families(fontServer, &families, &numFamilies); - + for (int currentFamily=0; currentFamily < numFamilies; currentFamily++) { - + // Gather the styles for this family PangoFontFace** faces = NULL; int numFaces = 0; pango_font_family_list_faces(families[currentFamily], &faces, &numFaces); - + for (int currentFace=0; currentFace < numFaces; currentFace++) { - - // If the face has a name, describe it, and then use the + + // If the face has a name, describe it, and then use the // description to get the UI family and face strings - + if (pango_font_face_get_face_name(faces[currentFace]) == NULL) { continue; } - + PangoFontDescription *faceDescr = pango_font_face_describe(faces[currentFace]); if (faceDescr) { Glib::ustring familyUIName = GetUIFamilyString(faceDescr); Glib::ustring styleUIName = GetUIStyleString(faceDescr); - + if (!familyUIName.empty() && !styleUIName.empty()) { // Find the right place to put the style information, adding // a map entry for the family name if it doesn't yet exist - + FamilyToStylesMap::iterator iter = map->find(familyUIName); - + if (iter == map->end()) { map->insert(std::make_pair(familyUIName, std::list<Glib::ustring>())); } - + // Insert into the style list and save the info in the reference maps // only if the style does not yet exist - + bool exists = false; std::list<Glib::ustring> &styleList = (*map)[familyUIName]; - + for (std::list<Glib::ustring>::iterator it=styleList.begin(); it != styleList.end(); it++) { @@ -620,10 +646,10 @@ void font_factory::GetUIFamiliesAndStyles(FamilyToStylesMap *map) break; } } - + if (!exists) { styleList.push_back(styleUIName); - + // Add the string info needed in the reference maps fontStringMap.insert( std::make_pair( @@ -640,7 +666,7 @@ void font_factory::GetUIFamiliesAndStyles(FamilyToStylesMap *map) } } } - + // Sort the style lists for (FamilyToStylesMap::iterator iter = map->begin() ; iter != map->end(); iter++) { (*iter).second.sort(StyleNameCompareInternal); @@ -651,24 +677,24 @@ void font_factory::GetUIFamiliesAndStyles(FamilyToStylesMap *map) font_instance* font_factory::FaceFromStyle(SPStyle const *style) { font_instance *font = NULL; - + g_assert(style); - + if (style) { // First try to use the font specification if it is set if (style->text->font_specification.set && style->text->font_specification.value && *style->text->font_specification.value) { - + font = FaceFromFontSpecification(style->text->font_specification.value); } - + // If that failed, try using the CSS information in the style if (!font) { font = Face(style->text->font_family.value, font_style_to_pos(*style)); } } - + return font; } @@ -684,13 +710,13 @@ font_instance *font_factory::FaceFromDescr(char const *family, char const *style font_instance* font_factory::FaceFromUIStrings(char const *uiFamily, char const *uiStyle) { font_instance *fontInstance = NULL; - + g_assert(uiFamily && uiStyle); if (uiFamily && uiStyle) { Glib::ustring uiString = Glib::ustring(uiFamily) + Glib::ustring(uiStyle); - + UIStringToPangoStringMap::iterator uiToPangoIter = fontStringMap.find(uiString); - + if (uiToPangoIter != fontStringMap.end ()) { PangoStringToDescrMap::iterator pangoToDescrIter = fontInstanceMap.find((*uiToPangoIter).second); if (pangoToDescrIter != fontInstanceMap.end()) { @@ -701,56 +727,56 @@ font_instance* font_factory::FaceFromUIStrings(char const *uiFamily, char const } } } - + return fontInstance; } font_instance* font_factory::FaceFromPangoString(char const *pangoString) { font_instance *fontInstance = NULL; - + g_assert(pangoString); - + if (pangoString) { PangoFontDescription *descr = NULL; - + // First attempt to find the font specification in the reference map PangoStringToDescrMap::iterator it = fontInstanceMap.find(Glib::ustring(pangoString)); if (it != fontInstanceMap.end()) { descr = pango_font_description_copy((*it).second); } - + // Or create a font description from the string - this may fail or // produce unexpected results if the string does not have a good format if (!descr) { descr = pango_font_description_from_string(pangoString); } - + if (descr && (pango_font_description_get_family(descr) != NULL)) { fontInstance = Face(descr); } - + if (descr) { pango_font_description_free(descr); } } - + return fontInstance; } font_instance* font_factory::FaceFromFontSpecification(char const *fontSpecification) { font_instance *font = NULL; - + g_assert(fontSpecification); - + if (fontSpecification) { // How the string is used to reconstruct a font depends on how it // was constructed in ConstructFontSpecification. As it stands, // the font specification is a pango-created string font = FaceFromPangoString(fontSpecification); } - + return font; } @@ -764,9 +790,9 @@ font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) #else pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround) #endif - + font_instance *res = NULL; - + if ( loadedFaces.find(descr) == loadedFaces.end() ) { // not yet loaded PangoFont *nFace = NULL; @@ -784,7 +810,7 @@ font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) // duplicate FcPattern, the hard way res = new font_instance(); // store the descr of the font we asked for, since this is the key where we intend to put the font_instance at - // in the hash_map. the descr of the returned pangofont may differ from what was asked, so we don't know (at this + // in the hash_map. the descr of the returned pangofont may differ from what was asked, so we don't know (at this // point) whether loadedFaces[that_descr] is free or not (and overwriting an entry will bring deallocation problems) res->descr = pango_font_description_copy(descr); res->daddy = this; @@ -844,15 +870,15 @@ font_instance *font_factory::Face(char const *family, int variant, int style, in font_instance *font_factory::Face(char const *family, NRTypePosDef apos) { PangoFontDescription *temp_descr = pango_font_description_new(); - + pango_font_description_set_family(temp_descr, family); - + if ( apos.variant == NR_POS_VARIANT_SMALLCAPS ) { pango_font_description_set_variant(temp_descr, PANGO_VARIANT_SMALL_CAPS); } else { pango_font_description_set_variant(temp_descr, PANGO_VARIANT_NORMAL); } - + if ( apos.italic ) { pango_font_description_set_style(temp_descr, PANGO_STYLE_ITALIC); } else if ( apos.oblique ) { @@ -860,7 +886,7 @@ font_instance *font_factory::Face(char const *family, NRTypePosDef apos) } else { pango_font_description_set_style(temp_descr, PANGO_STYLE_NORMAL); } - + if ( apos.weight <= NR_POS_WEIGHT_ULTRA_LIGHT ) { pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRALIGHT); } else if ( apos.weight <= NR_POS_WEIGHT_LIGHT ) { @@ -874,7 +900,7 @@ font_instance *font_factory::Face(char const *family, NRTypePosDef apos) } else { pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_HEAVY); } - + if ( apos.stretch <= NR_POS_STRETCH_ULTRA_CONDENSED ) { pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_CONDENSED); } else if ( apos.stretch <= NR_POS_STRETCH_CONDENSED ) { @@ -890,7 +916,7 @@ font_instance *font_factory::Face(char const *family, NRTypePosDef apos) } else { pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_EXPANDED); } - + font_instance *res = Face(temp_descr); pango_font_description_free(temp_descr); return res; diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index 2003ca26b..2b4b80e7c 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -215,7 +215,7 @@ void Layout::print(SPPrintContext *ctx, void Layout::showGlyphs(CairoRenderContext *ctx) const { if (_input_stream.empty()) return; - + bool clip_mode = false;//(ctx->getRenderMode() == CairoRenderContext::RENDER_MODE_CLIP); std::vector<CairoGlyphInfo> glyphtext; @@ -243,14 +243,9 @@ void Layout::showGlyphs(CairoRenderContext *ctx) const continue; } - Geom::Matrix font_matrix; - if (_path_fitted == NULL) { - font_matrix = glyph_matrix; - font_matrix[4] = 0; - font_matrix[5] = 0; - } else { - font_matrix.setIdentity(); - } + Geom::Matrix font_matrix = glyph_matrix; + font_matrix[4] = 0; + font_matrix[5] = 0; Glib::ustring::const_iterator span_iter = span.input_stream_first_character; unsigned char_index = _glyphs[glyph_index].in_character; @@ -276,13 +271,10 @@ void Layout::showGlyphs(CairoRenderContext *ctx) const CairoGlyphInfo info; info.index = _glyphs[glyph_index].glyph; - if (_path_fitted == NULL) { - info.x = glyph_matrix[4]; - info.y = glyph_matrix[5]; - } else { - info.x = 0; - info.y = 0; - } + // this is the translation for x,y-offset + info.x = glyph_matrix[4]; + info.y = glyph_matrix[5]; + glyphtext.push_back(info); glyph_index++; @@ -291,26 +283,24 @@ void Layout::showGlyphs(CairoRenderContext *ctx) const && _path_fitted == NULL && NR::transform_equalp(font_matrix, glyph_matrix, NR_EPSILON) && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index); - + // remove vertical flip - font_matrix[3] *= -1.0; + Geom::Matrix flip_matrix; + flip_matrix.setIdentity(); + flip_matrix[3] = -1.0; + font_matrix = flip_matrix * font_matrix; SPStyle const *style = text_source->style; float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); - - if (_path_fitted) { - ctx->pushState(); - ctx->transform(&glyph_matrix); - } else if (opacity != 1.0) { + + if (opacity != 1.0) { ctx->pushState(); ctx->setStateForStyle(style); ctx->pushLayer(); } if (glyph_index - first_index > 0) ctx->renderGlyphtext(span.font->pFont, &font_matrix, glyphtext, style); - if (_path_fitted) - ctx->popState(); - else if (opacity != 1.0) { + if (opacity != 1.0) { ctx->popLayer(); ctx->popState(); } diff --git a/src/line-snapper.cpp b/src/line-snapper.cpp index 73f46c0a2..5d5a77280 100644 --- a/src/line-snapper.cpp +++ b/src/line-snapper.cpp @@ -14,7 +14,7 @@ #include <2geom/line.h> #include "line-snapper.h" #include "snapped-line.h" -#include <gtk/gtk.h> +//#include <gtk/gtk.h> #include "snap.h" Inkscape::LineSnapper::LineSnapper(SnapManager *sm, Geom::Coord const d) : Snapper(sm, d) @@ -30,7 +30,7 @@ void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, std::vector<SPItem const *> const */*it*/, std::vector<std::pair<Geom::Point, int> > */*unselected_nodes*/) const { - if (!(_snap_enabled && _snapmanager->snapprefs.getSnapFrom(t)) ) { + if (!(_snap_enabled && _snapmanager->snapprefs.getSnapFrom(t)) ) { return; } @@ -50,6 +50,15 @@ void Inkscape::LineSnapper::freeSnap(SnappedConstraints &sc, //Store any line that's within snapping range if (dist < getSnapperTolerance()) { _addSnappedLine(sc, p_proj, dist, source_type, 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); + if (dist_p1 < getSnapperTolerance()) { + _addSnappedLinesOrigin(sc, p1, dist_p1, source_type); + // Only relevant for guides; grids don't have an origin per line + // Therefore _addSnappedLinesOrigin() will only be implemented for guides + } // std::cout << " -> distance = " << dist; } // std::cout << std::endl; @@ -75,35 +84,56 @@ void Inkscape::LineSnapper::constrainedSnap(SnappedConstraints &sc, 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 - Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; + // constraint line + Geom::Point const point_on_line = c.hasPoint() ? c.getPoint() : p; Geom::Line line1(point_on_line, point_on_line + c.getDirection()); - Geom::Line line2(i->second, i->second + Geom::rot90(i->first)); + + // grid/guide line + Geom::Point const p1 = i->second; // point at guide/grid line + Geom::Point const p2 = p1 + Geom::rot90(i->first); // 2nd point at guide/grid line + Geom::Line line2(p1, p2); + Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default try { - inters = Geom::intersection(line1, line2); + inters = Geom::intersection(line1, line2); } catch (Geom::InfiniteSolutions e) { - // We're probably dealing with parallel lines, so snapping doesn't make any sense here - continue; // jump to the next iterator in the for-loop + // We're probably dealing with parallel lines, so snapping doesn't make any sense here + continue; // jump to the next iterator in the for-loop } - if (inters) { - Geom::Point t = line1.pointAt((*inters).ta); - const Geom::Coord dist = Geom::L2(t - p); + if (inters) { + Geom::Point t = line1.pointAt((*inters).ta); + const Geom::Coord dist = Geom::L2(t - p); if (dist < getSnapperTolerance()) { - // When doing a constrained snap, we're already at an intersection. + // 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); + // 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); + if (dist_p1 < getSnapperTolerance()) { + _addSnappedLinesOrigin(sc, p1, dist_p1, source_type); + // Only relevant for guides; grids don't have an origin per line + // Therefore _addSnappedLinesOrigin() will only be implemented for guides + } } } } } } +// 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 +{ +} + /* Local Variables: mode:c++ diff --git a/src/line-snapper.h b/src/line-snapper.h index 4c971d238..4ad08a99f 100644 --- a/src/line-snapper.h +++ b/src/line-snapper.h @@ -7,7 +7,7 @@ * * Authors: * Carl Hetherington <inkscape@carlh.net> - * Diederik van Lierop <mail@diedenrezi.nl> + * Diederik van Lierop <mail@diedenrezi.nl> * * Copyright (C) 1999-2008 Authors * @@ -55,6 +55,10 @@ 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; + + // 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 _addSnappedPoint(SnappedConstraints &sc, Geom::Point const snapped_point, Geom::Coord const snapped_distance, SnapSourceType const &source) const = 0; }; diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp index 465bb205c..93280d794 100644 --- a/src/livarot/PathStroke.cpp +++ b/src/livarot/PathStroke.cpp @@ -63,7 +63,13 @@ void Path::Stroke(Shape *dest, bool doClose, double width, JoinType join, } if ( lastP > lastM+1 ) { - if ( pts[lastP - 1].closed ) { + Geom::Point sbStart = pts[lastM].p; + Geom::Point sbEnd = pts[lastP - 1].p; + // if ( pts[lastP - 1].closed ) { // this is correct, but this bugs text rendering (doesn't close text stroke)... + if ( Geom::LInfty(sbEnd-sbStart) < 0.00001 ) { // why close lines that shouldn't be closed? + // ah I see, because close is defined here for + // a whole path and should be defined per subpath. + // debut==fin => ferme (on devrait garder un element pour les close(), mais tant pis) DoStroke(lastM, lastP - lastM, dest, true, width, join, butt, miter, true); } else { DoStroke(lastM, lastP - lastM, dest, doClose, width, join, butt, miter, true); diff --git a/src/livarot/ShapeDraw.cpp b/src/livarot/ShapeDraw.cpp index f3a8dbd1f..d222cc49c 100644 --- a/src/livarot/ShapeDraw.cpp +++ b/src/livarot/ShapeDraw.cpp @@ -24,7 +24,6 @@ Shape::Plot (double ix, double iy, double ir, double mx, double my, bool doPoint fprintf(outFile,"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"); fprintf(outFile,"<svg:svg\n"); fprintf(outFile," id=\"svg1\"\n"); - fprintf(outFile," sodipodi:version=\"0.32\"\n"); fprintf(outFile," inkscape:version=\"0.38cvs\"\n"); fprintf(outFile," xmlns:svg=\"http://www.w3.org/2000/svg\"\n"); fprintf(outFile," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"); diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert index d992dd7bb..1cc2e1b69 100644 --- a/src/live_effects/Makefile_insert +++ b/src/live_effects/Makefile_insert @@ -18,6 +18,8 @@ ink_common_sources += \ live_effects/lpe-boolops.h \ live_effects/lpe-dynastroke.cpp \ live_effects/lpe-dynastroke.h \ + live_effects/lpe-extrude.cpp \ + live_effects/lpe-extrude.h \ live_effects/lpe-sketch.cpp \ live_effects/lpe-sketch.h \ live_effects/lpe-knot.cpp \ diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 1911c6e20..6f1004ae5 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -47,6 +47,7 @@ enum EffectType { DOEFFECTSTACK_TEST, DYNASTROKE, RECURSIVE_SKELETON, + EXTRUDE, INVALID_LPE // This must be last (I made it such that it is not needed anymore I think..., Don't trust on it being last. - johan) }; diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index de0535448..9232792f6 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -29,6 +29,7 @@ #include "message-stack.h" #include "desktop.h" #include "nodepath.h" +#include "knotholder.h" #include "live_effects/lpeobject.h" #include "live_effects/parameter/parameter.h" @@ -74,6 +75,7 @@ #include "live_effects/lpe-path_length.h" #include "live_effects/lpe-line_segment.h" #include "live_effects/lpe-recursiveskeleton.h" +#include "live_effects/lpe-extrude.h" namespace Inkscape { @@ -90,6 +92,7 @@ const Util::EnumData<EffectType> LPETypeData[] = { {CIRCLE_WITH_RADIUS, N_("Circle (by center and radius)"), "circle_with_radius"}, {CIRCLE_3PTS, N_("Circle by 3 points"), "circle_3pts"}, {DYNASTROKE, N_("Dynamic stroke"), "dynastroke"}, + {EXTRUDE, N_("Extrude"), "extrude"}, {LATTICE, N_("Lattice Deformation"), "lattice"}, {LINE_SEGMENT, N_("Line Segment"), "line_segment"}, {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"}, @@ -232,6 +235,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case RECURSIVE_SKELETON: neweffect = static_cast<Effect*> ( new LPERecursiveSkeleton(lpeobj) ); break; + case EXTRUDE: + neweffect = static_cast<Effect*> ( new LPEExtrude(lpeobj) ); + break; default: g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); neweffect = NULL; @@ -505,7 +511,7 @@ Effect::getHelperPaths(SPLPEItem *lpeitem) std::vector<Geom::PathVector> hp_vec; if (!SP_IS_SHAPE(lpeitem)) { - g_print ("How to handle helperpaths for non-shapes?\n"); // non-shapes are for example SPGroups. +// g_print ("How to handle helperpaths for non-shapes?\n"); // non-shapes are for example SPGroups. return hp_vec; } diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index 6f195b176..5d67ed016 100644 --- a/src/live_effects/effect.h +++ b/src/live_effects/effect.h @@ -13,11 +13,10 @@ #include "display/display-forward.h" #include <map> #include <glibmm/ustring.h> -#include <2geom/path.h> #include <2geom/forward.h> #include "ui/widget/registry.h" #include "sp-lpe-item.h" -#include "knotholder.h" +//#include "knotholder.h" #include "parameter/bool.h" #include "effect-enum.h" @@ -28,6 +27,9 @@ struct SPDesktop; struct SPItem; class SPNodeContext; struct LivePathEffectObject; +class SPLPEItem; +class KnotHolder; +class KnotHolderEntity; namespace Gtk { class Widget; diff --git a/src/live_effects/lpe-bendpath.cpp b/src/live_effects/lpe-bendpath.cpp index a820fe478..bc8477829 100644 --- a/src/live_effects/lpe-bendpath.cpp +++ b/src/live_effects/lpe-bendpath.cpp @@ -35,7 +35,7 @@ B is a map t --> B(t) = ( a(t), b(t) ). The first step is to re-parametrize B by its arc length: this is the parametrization in which a point p on B is located by its distance s from start. One obtains a new map s --> U(s) = (a'(s),b'(s)), that still describes the same path B, but where the distance along B from start to U(s) is s itself. -We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90°. Call this normal vector N(s). +We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90�. Call this normal vector N(s). The basic deformation associated to B is then given by: @@ -100,11 +100,14 @@ LPEBendPath::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd Piecewise<SBasis> x = vertical_pattern.get_value() ? Piecewise<SBasis>(patternd2[1]) : Piecewise<SBasis>(patternd2[0]); Piecewise<SBasis> y = vertical_pattern.get_value() ? Piecewise<SBasis>(patternd2[0]) : Piecewise<SBasis>(patternd2[1]); -//We use the group bounding box size or the path bbox size to translate well x and y - x-= vertical_pattern.get_value() ? boundingbox_Y.min() : boundingbox_X.min(); - y-= vertical_pattern.get_value() ? boundingbox_X.middle() : boundingbox_Y.middle(); + Interval bboxHorizontal = vertical_pattern.get_value() ? boundingbox_Y : boundingbox_X; + Interval bboxVertical = vertical_pattern.get_value() ? boundingbox_X : boundingbox_Y; - double scaling = uskeleton.cuts.back()/boundingbox_X.extent(); + //We use the group bounding box size or the path bbox size to translate well x and y + x-= bboxHorizontal.min(); + y-= bboxVertical.middle(); + + double scaling = uskeleton.cuts.back()/bboxHorizontal.extent(); if (scaling != 1.0) { x*=scaling; diff --git a/src/live_effects/lpe-envelope.cpp b/src/live_effects/lpe-envelope.cpp index a730f14ff..abd975b4e 100755 --- a/src/live_effects/lpe-envelope.cpp +++ b/src/live_effects/lpe-envelope.cpp @@ -69,6 +69,15 @@ LPEEnvelope::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd using namespace Geom; + // Don't allow empty path parameters: + if ( bend_path1.get_pathvector().empty() + || bend_path2.get_pathvector().empty() + || bend_path3.get_pathvector().empty() + || bend_path4.get_pathvector().empty() ) + { + return pwd2_in; + } + /* The code below is inspired from the Bend Path code developed by jfb and mgsloan Please, read it before tring to understand this one diff --git a/src/live_effects/lpe-extrude.cpp b/src/live_effects/lpe-extrude.cpp new file mode 100644 index 000000000..93ab60fc5 --- /dev/null +++ b/src/live_effects/lpe-extrude.cpp @@ -0,0 +1,145 @@ +#define INKSCAPE_LPE_EXTRUDE_CPP +/** \file + * @brief LPE effect for extruding paths (making them "3D"). + * + */ +/* Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * + * Copyright (C) 2009 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-extrude.h" + +#include <2geom/path.h> +#include <2geom/piecewise.h> +#include <2geom/transforms.h> + +namespace Inkscape { +namespace LivePathEffect { + +LPEExtrude::LPEExtrude(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + extrude_vector(_("Direction"), _("Defines the direction and magnitude of the extrusion"), "extrude_vector", &wr, this, Geom::Point(-10,10)) +{ + show_orig_path = true; + concatenate_before_pwd2 = false; + + registerParameter( dynamic_cast<Parameter *>(&extrude_vector) ); +} + +LPEExtrude::~LPEExtrude() +{ + +} + + +Geom::Piecewise<Geom::D2<Geom::SBasis> > +LPEExtrude::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) +{ + using namespace Geom; + + // generate connecting lines (the 'sides' of the extrusion) + Path path(Point(0.,0.)); + path.appendNew<Geom::LineSegment>( extrude_vector.getVector() ); + Piecewise<D2<SBasis> > connector = path.toPwSb(); + + switch( 1 ) { + case 0: { + Piecewise<D2<SBasis> > pwd2_out = pwd2_in; + // generate extrusion bottom: (just a copy of original path, displaced a bit) + pwd2_out.concat( pwd2_in + extrude_vector.getVector() ); + + // connecting lines should be put at start and end of path if it is not closed + // it is not possible to check whether a piecewise<T> path is closed, + // so we check whether start and end are close + if ( ! are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) { + pwd2_out.concat( connector + pwd2_in.firstValue() ); + pwd2_out.concat( connector + pwd2_in.lastValue() ); + } + // connecting lines should be put at cusps + Piecewise<D2<SBasis> > deriv = derivative(pwd2_in); + std::vector<double> cusps; // = roots(deriv); + for (unsigned i = 0; i < cusps.size() ; ++i) { + pwd2_out.concat( connector + pwd2_in.valueAt(cusps[i]) ); + } + // connecting lines should be put where the tangent of the path equals the extrude_vector in direction + std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector()))); + for (unsigned i = 0; i < rts.size() ; ++i) { + pwd2_out.concat( connector + pwd2_in.valueAt(rts[i]) ); + } + return pwd2_out; + } + + case 1: { + Piecewise<D2<SBasis> > pwd2_out; + bool closed_path = are_near(pwd2_in.firstValue(), pwd2_in.lastValue()); + // split input path in pieces between points where deriv == vector + Piecewise<D2<SBasis> > deriv = derivative(pwd2_in); + std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector()))); + double portion_t = 0.; + for (unsigned i = 0; i < rts.size() ; ++i) { + Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, rts[i] ); + portion_t = rts[i]; + if (closed_path && i == 0) { + // if the path is closed, skip the first cut and add it to the last cut later + continue; + } + Piecewise<D2<SBasis> > part = cut; + part.continuousConcat(connector + cut.lastValue()); + part.continuousConcat(reverse(cut) + extrude_vector.getVector()); + part.continuousConcat(reverse(connector) + cut.firstValue()); + pwd2_out.concat( part ); + } + if (closed_path) { + Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() ); + cut.continuousConcat(portion(pwd2_in, pwd2_in.domain().min(), rts[0] )); + Piecewise<D2<SBasis> > part = cut; + part.continuousConcat(connector + cut.lastValue()); + part.continuousConcat(reverse(cut) + extrude_vector.getVector()); + part.continuousConcat(reverse(connector) + cut.firstValue()); + pwd2_out.concat( part ); + } else if (!are_near(portion_t, pwd2_in.domain().max())) { + Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() ); + Piecewise<D2<SBasis> > part = cut; + part.continuousConcat(connector + cut.lastValue()); + part.continuousConcat(reverse(cut) + extrude_vector.getVector()); + part.continuousConcat(reverse(connector) + cut.firstValue()); + pwd2_out.concat( part ); + } + return pwd2_out; + } + } +} + +void +LPEExtrude::resetDefaults(SPItem * item) +{ + Effect::resetDefaults(item); + + using namespace Geom; + + Geom::OptRect bbox = item->getBounds(Geom::identity(), SPItem::GEOMETRIC_BBOX); + if (bbox) { + Interval boundingbox_X = (*bbox)[Geom::X]; + Interval boundingbox_Y = (*bbox)[Geom::Y]; + extrude_vector.set_and_write_new_values( Geom::Point(boundingbox_X.middle(), boundingbox_Y.middle()), + (boundingbox_X.extent() + boundingbox_Y.extent())*Geom::Point(-0.05,0.2) ); + } +} + +} //namespace LivePathEffect +} /* namespace Inkscape */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/live_effects/lpe-extrude.h b/src/live_effects/lpe-extrude.h new file mode 100644 index 000000000..b704aa856 --- /dev/null +++ b/src/live_effects/lpe-extrude.h @@ -0,0 +1,52 @@ +/** @file + * @brief LPE effect for extruding paths (making them "3D"). + */ +/* Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * + * Copyright (C) 2009 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_LPE_EXTRUDE_H +#define INKSCAPE_LPE_EXTRUDE_H + +#include "live_effects/effect.h" +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/vector.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEExtrude : public Effect { +public: + LPEExtrude(LivePathEffectObject *lpeobject); + virtual ~LPEExtrude(); + + virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); + + virtual void resetDefaults(SPItem * item); + +private: + VectorParam extrude_vector; + + LPEExtrude(const LPEExtrude&); + LPEExtrude& operator=(const LPEExtrude&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/live_effects/lpe-interpolate.cpp b/src/live_effects/lpe-interpolate.cpp index e19d2e6e7..e77a392e9 100644 --- a/src/live_effects/lpe-interpolate.cpp +++ b/src/live_effects/lpe-interpolate.cpp @@ -52,8 +52,13 @@ LPEInterpolate::~LPEInterpolate() Geom::PathVector LPEInterpolate::doEffect_path (Geom::PathVector const & path_in) { - if ( (path_in.size() < 2) || (number_of_steps < 2)) + if ( (path_in.size() < 2) || (number_of_steps < 2)) { return path_in; + } + // Don't allow empty path parameter: + if ( trajectory_path.get_pathvector().empty() ) { + return path_in; + } std::vector<Geom::Path> path_out; diff --git a/src/live_effects/lpe-knot.cpp b/src/live_effects/lpe-knot.cpp index f6cf5ea78..b3aa2df45 100644 --- a/src/live_effects/lpe-knot.cpp +++ b/src/live_effects/lpe-knot.cpp @@ -10,10 +10,12 @@ */ #include "sp-shape.h" +#include "sp-path.h" #include "display/curve.h" #include "live_effects/lpe-knot.h" #include "svg/svg.h" #include "style.h" +#include "knot-holder-entity.h" #include <2geom/sbasis-to-bezier.h> #include <2geom/sbasis.h> @@ -25,6 +27,10 @@ #include <2geom/basic-intersection.h> #include <2geom/exception.h> +// for change crossing undo +#include "verbs.h" +#include "document.h" + #include <exception> namespace Inkscape { @@ -320,10 +326,10 @@ CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value) LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) : Effect(lpeobject), // initialise your parameters here: - interruption_width(_("Interruption width"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3), - prop_to_stroke_width(_("unit of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width."), "prop_to_stroke_width", &wr, this, true), - add_stroke_width(_("add stroke width to interruption size"), _("Add the stroke width to the interruption size."), "add_stroke_width", &wr, this, true), - add_other_stroke_width(_("add other's stroke width to interruption size"), _("Add crossed stroke width to the interruption size."), "add_other_stroke_width", &wr, this, true), + interruption_width(_("Fixed width"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3), + prop_to_stroke_width(_("In units of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width"), "prop_to_stroke_width", &wr, this, true), + add_stroke_width(_("Stroke width"), _("Add the stroke width to the interruption size"), "add_stroke_width", &wr, this, true), + add_other_stroke_width(_("Crossing path stroke width"), _("Add crossed stroke width to the interruption size"), "add_other_stroke_width", &wr, this, true), switcher_size(_("Switcher size"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15), crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this), gpaths(),gstroke_widths() @@ -486,7 +492,12 @@ void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &p } } else if (SP_IS_SHAPE(lpeitem)) { - SPCurve * c = sp_shape_get_curve(SP_SHAPE(lpeitem)); + SPCurve * c = NULL; + if (SP_IS_PATH(lpeitem)) { + c = sp_path_get_curve_for_edit(SP_PATH(lpeitem)); + } else { + c = sp_shape_get_curve(SP_SHAPE(lpeitem)); + } if (c) { Geom::PathVector subpaths = c->get_pathvector(); for (unsigned i=0; i<subpaths.size(); i++){ @@ -623,9 +634,11 @@ KnotHolderEntityCrossingSwitcher::knot_click(guint state) //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n"; } lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector()); + sp_document_done(lpe->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, /// @todo Is this the right verb? + _("Change knot crossing")); // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating. - sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); +// sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); } } diff --git a/src/live_effects/lpe-mirror_symmetry.cpp b/src/live_effects/lpe-mirror_symmetry.cpp index cd724b70e..a3a0faf37 100644 --- a/src/live_effects/lpe-mirror_symmetry.cpp +++ b/src/live_effects/lpe-mirror_symmetry.cpp @@ -60,6 +60,11 @@ LPEMirrorSymmetry::doOnApply (SPLPEItem *lpeitem) std::vector<Geom::Path> LPEMirrorSymmetry::doEffect_path (std::vector<Geom::Path> const & path_in) { + // Don't allow empty path parameter: + if ( reflection_line.get_pathvector().empty() ) { + return path_in; + } + std::vector<Geom::Path> path_out; if (!discard_orig_path) { path_out = path_in; diff --git a/src/live_effects/lpe-patternalongpath.cpp b/src/live_effects/lpe-patternalongpath.cpp index 29c5517bf..45b2b67b4 100644 --- a/src/live_effects/lpe-patternalongpath.cpp +++ b/src/live_effects/lpe-patternalongpath.cpp @@ -33,7 +33,7 @@ B is a map t --> B(t) = ( a(t), b(t) ). The first step is to re-parametrize B by its arc length: this is the parametrization in which a point p on B is located by its distance s from start. One obtains a new map s --> U(s) = (a'(s),b'(s)), that still describes the same path B, but where the distance along B from start to U(s) is s itself. -We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90°. Call this normal vector N(s). +We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90�. Call this normal vector N(s). The basic deformation associated to B is then given by: @@ -104,6 +104,11 @@ LPEPatternAlongPath::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > con { using namespace Geom; + // Don't allow empty path parameter: + if ( pattern.get_pathvector().empty() ) { + return pwd2_in; + } + /* Much credit should go to jfb and mgsloan of lib2geom development for the code below! */ Piecewise<D2<SBasis> > output; std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > pre_output; diff --git a/src/live_effects/lpe-perspective_path.cpp b/src/live_effects/lpe-perspective_path.cpp index d5262f33c..091a4d9ae 100644 --- a/src/live_effects/lpe-perspective_path.cpp +++ b/src/live_effects/lpe-perspective_path.cpp @@ -16,6 +16,7 @@ #include "live_effects/lpe-perspective_path.h" #include "sp-item-group.h" +#include "knot-holder-entity.h" #include "inkscape.h" diff --git a/src/live_effects/lpe-rough-hatches.cpp b/src/live_effects/lpe-rough-hatches.cpp index 4df1064ce..bcfd0f373 100644 --- a/src/live_effects/lpe-rough-hatches.cpp +++ b/src/live_effects/lpe-rough-hatches.cpp @@ -226,24 +226,24 @@ LPERoughHatches::LPERoughHatches(LivePathEffectObject *lpeobject) : dist_rdm(_("Frequency randomness"), _("Variation of distance between hatches, in %."), "dist_rdm", &wr, this, 75), growth(_("Growth"), _("Growth of distance between hatches."), "growth", &wr, this, 0.), //FIXME: top/bottom names are inverted in the UI/svg and in the code!! - scale_tf(_("Half turns smoothness: 1st side, in"), _("Set smoothness/sharpness of path when reaching a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bf", &wr, this, 1.), - scale_tb(_("1st side, out"), _("Set smoothness/sharpness of path when leaving a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bb", &wr, this, 1.), - scale_bf(_("2nd side, in"), _("Set smoothness/sharpness of path when reaching a 'top' halfturn. 0=sharp, 1=default"), "scale_tf", &wr, this, 1.), - scale_bb(_("2nd side, out"), _("Set smoothness/sharpness of path when leaving a 'top' halfturn. 0=sharp, 1=default"), "scale_tb", &wr, this, 1.), - top_edge_variation(_("Magnitude jitter: 1st side"), _("Randomly moves 'bottom' halfsturns to produce magnitude variations."), "bottom_edge_variation", &wr, this, 0), - bot_edge_variation(_("2nd side"), _("Randomly moves 'top' halfsturns to produce magnitude variations."), "top_edge_variation", &wr, this, 0), - top_tgt_variation(_("Parallelism jitter: 1st side"), _("Add direction randomness by moving 'bottom' halfsturns tangentially to the boundary."), "bottom_tgt_variation", &wr, this, 0), - bot_tgt_variation(_("2nd side"), _("Add direction randomness by randomly moving 'top' halfsturns tangentially to the boundary."), "top_tgt_variation", &wr, this, 0), - top_smth_variation(_("Variance: 1st side"), _("Randomness of 'bottom' halfturns smoothness"), "top_smth_variation", &wr, this, 0), - bot_smth_variation(_("2nd side"), _("Randomness of 'top' halfturns smoothness"), "bottom_smth_variation", &wr, this, 0), + scale_tf(_("Half-turns smoothness: 1st side, in"), _("Set smoothness/sharpness of path when reaching a 'bottom' half-turn. 0=sharp, 1=default"), "scale_bf", &wr, this, 1.), + scale_tb(_("1st side, out"), _("Set smoothness/sharpness of path when leaving a 'bottom' half-turn. 0=sharp, 1=default"), "scale_bb", &wr, this, 1.), + scale_bf(_("2nd side, in"), _("Set smoothness/sharpness of path when reaching a 'top' half-turn. 0=sharp, 1=default"), "scale_tf", &wr, this, 1.), + scale_bb(_("2nd side, out"), _("Set smoothness/sharpness of path when leaving a 'top' half-turn. 0=sharp, 1=default"), "scale_tb", &wr, this, 1.), + top_edge_variation(_("Magnitude jitter: 1st side"), _("Randomly moves 'bottom' half-turns to produce magnitude variations."), "bottom_edge_variation", &wr, this, 0), + bot_edge_variation(_("2nd side"), _("Randomly moves 'top' half-turns to produce magnitude variations."), "top_edge_variation", &wr, this, 0), + top_tgt_variation(_("Parallelism jitter: 1st side"), _("Add direction randomness by moving 'bottom' half-turns tangentially to the boundary."), "bottom_tgt_variation", &wr, this, 0), + bot_tgt_variation(_("2nd side"), _("Add direction randomness by randomly moving 'top' half-turns tangentially to the boundary."), "top_tgt_variation", &wr, this, 0), + top_smth_variation(_("Variance: 1st side"), _("Randomness of 'bottom' half-turns smoothness"), "top_smth_variation", &wr, this, 0), + bot_smth_variation(_("2nd side"), _("Randomness of 'top' half-turns smoothness"), "bottom_smth_variation", &wr, this, 0), // - fat_output(_("Generate thick/thin path"), _("Simulate a stroke of varrying width"), "fat_output", &wr, this, true), + fat_output(_("Generate thick/thin path"), _("Simulate a stroke of varying width"), "fat_output", &wr, this, true), do_bend(_("Bend hatches"), _("Add a global bend to the hatches (slower)"), "do_bend", &wr, this, true), - stroke_width_top(_("Thickness: at 1st side"), _("Width at 'bottom' half turns"), "stroke_width_top", &wr, this, 1.), - stroke_width_bot(_("at 2nd side"), _("Width at 'top' halfturns"), "stroke_width_bottom", &wr, this, 1.), + stroke_width_top(_("Thickness: at 1st side"), _("Width at 'bottom' half-turns"), "stroke_width_top", &wr, this, 1.), + stroke_width_bot(_("at 2nd side"), _("Width at 'top' half-turns"), "stroke_width_bottom", &wr, this, 1.), // - front_thickness(_("from 2nd to 1st side"), _("Width of paths from 'top' to 'bottom' halfturns"), "front_thickness", &wr, this, 1.), - back_thickness(_("from 1st to 2nd side"), _("Width of paths from 'top' to 'bottom' halfturns"), "back_thickness", &wr, this, .25), + front_thickness(_("from 2nd to 1st side"), _("Width from 'top' to 'bottom'"), "front_thickness", &wr, this, 1.), + back_thickness(_("from 1st to 2nd side"), _("Width from 'bottom' to 'top'"), "back_thickness", &wr, this, .25), direction(_("Hatches width and dir"), _("Defines hatches frequency and direction"), "direction", &wr, this, Geom::Point(50,0)), // diff --git a/src/live_effects/lpe-spiro.cpp b/src/live_effects/lpe-spiro.cpp index 72b77622d..794fd980e 100644 --- a/src/live_effects/lpe-spiro.cpp +++ b/src/live_effects/lpe-spiro.cpp @@ -158,7 +158,9 @@ LPESpiro::doEffect(SPCurve * curve) Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength. - if (path_it->back_closed().isDegenerate()) { + const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type Geom::LineSegment. + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + // closingline.isDegenerate() did not work, because it only checks for *exact* zero length, which goes wrong for relative coordinates and rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } diff --git a/src/live_effects/lpe-vonkoch.cpp b/src/live_effects/lpe-vonkoch.cpp index 32fb763d3..7fd0ac0b4 100644 --- a/src/live_effects/lpe-vonkoch.cpp +++ b/src/live_effects/lpe-vonkoch.cpp @@ -49,7 +49,7 @@ LPEVonKoch::LPEVonKoch(LivePathEffectObject *lpeobject) : similar_only(_("Use uniform transforms only"), _("2 consecutive segments are used to reverse/preserve orientation only (otherwise, they define a general transform)."), "similar_only", &wr, this, false), drawall(_("Draw all generations"), _("If unchecked, draw only the last generation"), "drawall", &wr, this, true), //,draw_boxes(_("Display boxes"), _("Display boxes instead of paths only"), "draw_boxes", &wr, this, true) - ref_path(_("Reference segment"), _("The reference segment. Defaults to bbox diameter."), "ref_path", &wr, this, "M0,0 L10,0"), + ref_path(_("Reference segment"), _("The reference segment. Defaults to the horizontal midline of the bbox."), "ref_path", &wr, this, "M0,0 L10,0"), //refA(_("Ref Start"), _("Left side middle of the reference box"), "refA", &wr, this), //refB(_("Ref End"), _("Right side middle of the reference box"), "refB", &wr, this), //FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug. diff --git a/src/live_effects/lpeobject.h b/src/live_effects/lpeobject.h index dc631a5c1..9f802643b 100644 --- a/src/live_effects/lpeobject.h +++ b/src/live_effects/lpeobject.h @@ -10,13 +10,16 @@ */ #include "sp-object.h" -#include "effect.h" +#include "effect-enum.h" namespace Inkscape { -namespace XML { -class Node; -class Document; -} + namespace XML { + class Node; + class Document; + } + namespace LivePathEffect { + class Effect; + } } #define TYPE_LIVEPATHEFFECT (LivePathEffectObject::livepatheffect_get_type()) diff --git a/src/live_effects/parameter/point.cpp b/src/live_effects/parameter/point.cpp index 057cc424b..e7abb70ea 100644 --- a/src/live_effects/parameter/point.cpp +++ b/src/live_effects/parameter/point.cpp @@ -16,6 +16,7 @@ #include "ui/widget/registered-widget.h" #include "inkscape.h" #include "verbs.h" +#include "knotholder.h" // needed for on-canvas editting: #include "desktop.h" diff --git a/src/live_effects/spiro.cpp b/src/live_effects/spiro.cpp index 612348ab8..abc9c94ca 100644 --- a/src/live_effects/spiro.cpp +++ b/src/live_effects/spiro.cpp @@ -731,6 +731,10 @@ spiro_iter(spiro_seg *s, bandmat *m, int *perm, double *v, int n) add_mat_line(m, v, derivs[1][1], -ends[1][1], 1, j, jk0r, jinc, nmat); add_mat_line(m, v, derivs[2][1], -ends[1][2], 1, j, jk1r, jinc, nmat); add_mat_line(m, v, derivs[3][1], -ends[1][3], 1, j, jk2r, jinc, nmat); + if (jthl >= 0) + v[jthl] = mod_2pi(v[jthl]); + if (jthr >= 0) + v[jthr] = mod_2pi(v[jthr]); j += jinc; } if (cyclic) { @@ -771,6 +775,7 @@ spiro_iter(spiro_seg *s, bandmat *m, int *perm, double *v, int n) s[i].ks[k] += dk; norm += dk * dk; } + s[i].ks[0] = 2.0*mod_2pi(s[i].ks[0]/2.0); } return norm; } diff --git a/src/main.cpp b/src/main.cpp index 9c33688ed..f96d99e11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,7 @@ #include <extension/system.h> #include <extension/db.h> #include <extension/output.h> +#include <extension/input.h> #ifdef WIN32 //#define REPLACEARGS_ANSI @@ -129,7 +130,7 @@ enum { SP_ARG_EXPORT_DPI, SP_ARG_EXPORT_AREA, SP_ARG_EXPORT_AREA_DRAWING, - SP_ARG_EXPORT_AREA_CANVAS, + SP_ARG_EXPORT_AREA_PAGE, SP_ARG_EXPORT_AREA_SNAP, SP_ARG_EXPORT_WIDTH, SP_ARG_EXPORT_HEIGHT, @@ -179,7 +180,7 @@ static gchar *sp_export_png = NULL; static gchar *sp_export_dpi = NULL; static gchar *sp_export_area = NULL; static gboolean sp_export_area_drawing = FALSE; -static gboolean sp_export_area_canvas = FALSE; +static gboolean sp_export_area_page = FALSE; static gchar *sp_export_width = NULL; static gchar *sp_export_height = NULL; static gchar *sp_export_id = NULL; @@ -222,7 +223,7 @@ static void resetCommandlineGlobals() { sp_export_dpi = NULL; sp_export_area = NULL; sp_export_area_drawing = FALSE; - sp_export_area_canvas = FALSE; + sp_export_area_page = FALSE; sp_export_width = NULL; sp_export_height = NULL; sp_export_id = NULL; @@ -296,17 +297,17 @@ struct poptOption options[] = { {"export-area", 'a', POPT_ARG_STRING, &sp_export_area, SP_ARG_EXPORT_AREA, - N_("Exported area in SVG user units (default is the canvas; 0,0 is lower-left corner)"), + N_("Exported area in SVG user units (default is the page; 0,0 is lower-left corner)"), N_("x0:y0:x1:y1")}, {"export-area-drawing", 'D', POPT_ARG_NONE, &sp_export_area_drawing, SP_ARG_EXPORT_AREA_DRAWING, - N_("Exported area is the entire drawing (not canvas)"), + N_("Exported area is the entire drawing (not page)"), NULL}, - {"export-area-canvas", 'C', - POPT_ARG_NONE, &sp_export_area_canvas, SP_ARG_EXPORT_AREA_CANVAS, - N_("Exported area is the entire canvas"), + {"export-area-page", 'C', + POPT_ARG_NONE, &sp_export_area_page, SP_ARG_EXPORT_AREA_PAGE, + N_("Exported area is the entire page"), NULL}, {"export-area-snap", 0, @@ -628,13 +629,13 @@ main(int argc, char **argv) || !strcmp(argv[i], "-e") || !strncmp(argv[i], "--export-png", 12) || !strcmp(argv[i], "-l") - || !strncmp(argv[i], "--export-plain-svg", 12) + || !strncmp(argv[i], "--export-plain-svg", 18) || !strcmp(argv[i], "-i") || !strncmp(argv[i], "--export-area-drawing", 21) || !strcmp(argv[i], "-D") - || !strncmp(argv[i], "--export-area-canvas", 20) + || !strncmp(argv[i], "--export-area-page", 18) || !strcmp(argv[i], "-C") - || !strncmp(argv[i], "--export-id", 12) + || !strncmp(argv[i], "--export-id", 11) || !strcmp(argv[i], "-P") || !strncmp(argv[i], "--export-ps", 11) || !strcmp(argv[i], "-E") @@ -652,11 +653,11 @@ main(int argc, char **argv) || !strcmp(argv[i], "-S") || !strncmp(argv[i], "--query-all", 11) || !strcmp(argv[i], "-X") - || !strncmp(argv[i], "--query-x", 13) + || !strncmp(argv[i], "--query-x", 9) || !strcmp(argv[i], "-Y") - || !strncmp(argv[i], "--query-y", 14) + || !strncmp(argv[i], "--query-y", 9) || !strcmp(argv[i], "--vacuum-defs") - || !strncmp(argv[i], "--shell", 7) + || !strcmp(argv[i], "--shell") ) { /* main_console handles any exports -- not the gui */ @@ -962,12 +963,27 @@ void sp_process_file_list(GSList *fl) { while (fl) { const gchar *filename = (gchar *)fl->data; - SPDocument *doc = Inkscape::Extension::open(NULL, filename); + + SPDocument *doc = NULL; + try { + doc = Inkscape::Extension::open(NULL, filename); + } catch (Inkscape::Extension::Input::no_extension_found &e) { + doc = NULL; + } catch (Inkscape::Extension::Input::open_failed &e) { + doc = NULL; + } + if (doc == NULL) { - doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), filename); + try { + doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), filename); + } catch (Inkscape::Extension::Input::no_extension_found &e) { + doc = NULL; + } catch (Inkscape::Extension::Input::open_failed &e) { + doc = NULL; + } } if (doc == NULL) { - g_warning("Specified document %s cannot be opened (is it a valid SVG file?)", filename); + g_warning("Specified document %s cannot be opened (does not exist or not a valid SVG file)", filename); } else { if (sp_vacuum_defs) { vacuum_document(doc); @@ -979,7 +995,7 @@ void sp_process_file_list(GSList *fl) if (sp_global_printer) { sp_print_document_to_file(doc, sp_global_printer); } - if (sp_export_png) { + if (sp_export_png || (sp_export_id && sp_export_use_hints)) { sp_do_export_png(doc); } if (sp_export_svg) { @@ -1208,6 +1224,7 @@ static void sp_do_export_png(SPDocument *doc) { const gchar *filename = NULL; + bool filename_from_hint = false; gdouble dpi = 0.0; if (sp_export_use_hints && (!sp_export_id && !sp_export_area_drawing)) { @@ -1254,6 +1271,7 @@ sp_do_export_png(SPDocument *doc) filename = sp_export_png; } else { filename = fn_hint; + filename_from_hint = true; } } else { g_warning ("Export filename hint not found for the object."); @@ -1298,8 +1316,8 @@ sp_do_export_png(SPDocument *doc) return; } area = Geom::Rect(Geom::Interval(x0,x1), Geom::Interval(y0,y1)); - } else if (sp_export_area_canvas || !(sp_export_id || sp_export_area_drawing)) { - /* Export the whole canvas */ + } else if (sp_export_area_page || !(sp_export_id || sp_export_area_drawing)) { + /* Export the whole page: note: Inkscape uses 'page' in all menus and dialogs, not 'canvas' */ sp_document_ensure_up_to_date (doc); Geom::Point origin (SP_ROOT(doc->root)->x.computed, SP_ROOT(doc->root)->y.computed); area = Geom::Rect(origin, origin + sp_document_dimensions(doc)); @@ -1393,6 +1411,23 @@ sp_do_export_png(SPDocument *doc) } } + gchar *path = 0; + if (filename_from_hint) { + //Make relative paths go from the document location, if possible: + if (!g_path_is_absolute(filename) && doc->uri) { + gchar *dirname = g_path_get_dirname(doc->uri); + if (dirname) { + path = g_build_filename(dirname, filename, NULL); + g_free(dirname); + } + } + if (!path) { + path = g_strdup(filename); + } + } else { + path = g_strdup(filename); + } + g_print("Background RRGGBBAA: %08x\n", bgcolor); g_print("Area %g:%g:%g:%g exported to %lu x %lu pixels (%g dpi)\n", area[Geom::X][0], area[Geom::Y][0], area[Geom::X][1], area[Geom::Y][1], width, height, dpi); @@ -1400,11 +1435,12 @@ sp_do_export_png(SPDocument *doc) g_print("Bitmap saved as: %s\n", filename); if ((width >= 1) && (height >= 1) && (width <= PNG_UINT_31_MAX) && (height <= PNG_UINT_31_MAX)) { - sp_export_png_file(doc, filename, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, sp_export_id_only ? items : NULL); + sp_export_png_file(doc, path, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, sp_export_id_only ? items : NULL); } else { g_warning("Calculated bitmap dimensions %lu %lu are out of range (1 - %lu). Nothing exported.", width, height, (unsigned long int)PNG_UINT_31_MAX); } + g_free (path); g_slist_free (items); } @@ -1443,8 +1479,8 @@ static void do_export_ps_pdf(SPDocument* doc, gchar const* uri, char const* mime (*i)->set_param_string ("exportId", ""); } - if (sp_export_area_canvas && sp_export_area_drawing) { - g_warning ("You cannot use --export-area-canvas and --export-area-drawing at the same time; only the former will take effect."); + if (sp_export_area_page && sp_export_area_drawing) { + g_warning ("You cannot use --export-area-page and --export-area-drawing at the same time; only the former will take effect."); sp_export_area_drawing = false; } @@ -1454,17 +1490,17 @@ static void do_export_ps_pdf(SPDocument* doc, gchar const* uri, char const* mime (*i)->set_param_bool ("areaDrawing", FALSE); } - if (sp_export_area_canvas) { + if (sp_export_area_page) { if (sp_export_eps) { - g_warning ("EPS cannot have its bounding box extend beyond its content, so if your drawing is smaller than the canvas, --export-area-canvas will clip it to drawing."); + g_warning ("EPS cannot have its bounding box extend beyond its content, so if your drawing is smaller than the page, --export-area-page will clip it to drawing."); } - (*i)->set_param_bool ("areaCanvas", TRUE); + (*i)->set_param_bool ("areaPage", TRUE); } else { - (*i)->set_param_bool ("areaCanvas", FALSE); + (*i)->set_param_bool ("areaPage", FALSE); } - if (!sp_export_area_drawing && !sp_export_area_canvas && !sp_export_id) { - // neither is set, set canvas as default for ps/pdf and drawing for eps + if (!sp_export_area_drawing && !sp_export_area_page && !sp_export_id) { + // neither is set, set page as default for ps/pdf and drawing for eps if (sp_export_eps) { try { (*i)->set_param_bool("areaDrawing", TRUE); diff --git a/src/marker.cpp b/src/marker.cpp index c66acc192..e4c2e0c30 100644 --- a/src/marker.cpp +++ b/src/marker.cpp @@ -440,22 +440,9 @@ sp_marker_update (SPObject *object, SPCtx *ctx, guint flags) } } - { - Geom::Matrix q; - /* Compose additional transformation from scale and position */ - q[0] = width / vb.width(); - q[1] = 0.0; - q[2] = 0.0; - q[3] = height / vb.height(); - q[4] = -vb.min()[Geom::X] * q[0] + x; - q[5] = -vb.min()[Geom::Y] * q[3] + y; - /* Append viewbox transformation */ - marker->c2p = q * marker->c2p; - } - - /* Append reference translation */ - /* fixme: lala (Lauris) */ - marker->c2p = Geom::Translate(-marker->refX.computed, -marker->refY.computed) * marker->c2p; + // viewbox transformation and reference translation + marker->c2p = Geom::Translate(-marker->refX.computed, -marker->refY.computed) * + Geom::Scale(width / vb.width(), height / vb.height()); rctx.i2doc = marker->c2p * rctx.i2doc; diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h index a4bdcfd9a..d7a18f211 100644 --- a/src/menus-skeleton.h +++ b/src/menus-skeleton.h @@ -29,7 +29,7 @@ static char const menus_skeleton[] = " <verb verb-id=\"FileExport\" />\n" #ifdef WITH_GNOME_VFS " <verb verb-id=\"FileImportFromOCAL\" />\n" -" <verb verb-id=\"FileExportToOCAL\" />\n" +//" <verb verb-id=\"FileExportToOCAL\" />\n" #endif " <separator/>\n" " <verb verb-id=\"FilePrint\" />\n" @@ -124,7 +124,7 @@ static char const menus_skeleton[] = " <separator/>\n" " <verb verb-id=\"DialogSwatches\" />\n" " <verb verb-id=\"DialogDebug\" />\n" -" <verb verb-id=\"DialogScript\" />\n" +//" <verb verb-id=\"DialogScript\" />\n" " <separator/>\n" " <verb verb-id=\"PrevWindow\" />\n" " <verb verb-id=\"NextWindow\" />\n" diff --git a/src/node-context.cpp b/src/node-context.cpp index 6ad7b85d4..7efa57290 100644 --- a/src/node-context.cpp +++ b/src/node-context.cpp @@ -117,6 +117,11 @@ sp_node_context_dispose(GObject *object) SPEventContext *ec = SP_EVENT_CONTEXT(object); ec->enableGrDrag(false); + + if (nc->grabbed) { + sp_canvas_item_ungrab(nc->grabbed, GDK_CURRENT_TIME); + nc->grabbed = NULL; + } nc->sel_changed_connection.disconnect(); nc->sel_changed_connection.~connection(); @@ -300,8 +305,6 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) nc->remove_flash_counter--; } - sp_event_context_snap_window_open(event_context, false); // Just put the snap window open, bluntly. Will be closed when we have left the context - gint ret = FALSE; switch (event->type) { case GDK_BUTTON_PRESS: @@ -329,6 +332,17 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) event->button.y); Geom::Point const button_dt(desktop->w2d(button_w)); Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + + if (nc->grabbed) { + sp_canvas_item_ungrab(nc->grabbed, event->button.time); + nc->grabbed = NULL; + } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + nc->grabbed = SP_CANVAS_ITEM(desktop->acetate); + nc->current_state = SP_NODE_CONTEXT_INACTIVE; desktop->updateNow(); ret = TRUE; @@ -462,6 +476,10 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) desktop->updateNow(); } Inkscape::Rubberband::get(desktop)->stop(); + if (nc->grabbed) { + sp_canvas_item_ungrab(nc->grabbed, event->button.time); + nc->grabbed = NULL; + } ret = TRUE; break; } @@ -485,6 +503,12 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) } ret = TRUE; Inkscape::Rubberband::get(desktop)->stop(); + + if (nc->grabbed) { + sp_canvas_item_ungrab(nc->grabbed, event->button.time); + nc->grabbed = NULL; + } + desktop->updateNow(); nc->rb_escaped = false; nc->drag = FALSE; diff --git a/src/node-context.h b/src/node-context.h index 0e141274f..2345ffc7e 100644 --- a/src/node-context.h +++ b/src/node-context.h @@ -57,6 +57,7 @@ struct SPNodeContext { unsigned int current_state; SPItem * flashed_item; + SPCanvasItem *grabbed; Inkscape::Display::TemporaryItem * flash_tempitem; int remove_flash_counter; }; diff --git a/src/nodepath.cpp b/src/nodepath.cpp index 8a47b658b..8f17ae013 100644 --- a/src/nodepath.cpp +++ b/src/nodepath.cpp @@ -201,6 +201,19 @@ sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) { } } +static void +sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) { + for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) { + for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) { + GtkObject *temp = *j; + *j = NULL; + gtk_object_destroy(temp); + } + } + np->helper_path_vec.clear(); +} + +/** updates canvas items from the effect's helper paths */ void sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) { //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec; @@ -211,13 +224,15 @@ sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) { SPLPEItem *lpeitem = SP_LPE_ITEM(np->item); PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem); + + /* The number or type or LPEs may have changed, so we need to clear and recreate our + * helper_path_vec to make sure it is in sync */ + sp_nodepath_destroy_helperpaths(np); + sp_nodepath_create_helperpaths(np); + for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) { Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe(); if (lpe) { - /* update canvas items from the effect's helper paths; note that this code relies on the - * fact that getHelperPaths() will always return the same number of helperpaths in the same - * order as during their creation in sp_nodepath_create_helperpaths - */ std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem); for (unsigned int j = 0; j < hpaths.size(); ++j) { SPCurve *curve = new SPCurve(hpaths[j]); @@ -229,19 +244,6 @@ sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) { } } -static void -sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) { - for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) { - for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) { - GtkObject *temp = *j; - *j = NULL; - gtk_object_destroy(temp); - } - } - np->helper_path_vec.clear(); -} - - /** * \brief Creates new nodepath from item * @@ -1199,7 +1201,9 @@ void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::Nod // pull opposite handle in line with the existing one } } else if (no_handles) { - if (both_segments_are_lines OR both_segments_are_curves) { + if (both_segments_are_lines + OR both_segments_are_curves + OR one_is_line_but_the_curveside_node_is_selected_and_has_two_handles) { //pull both handles } else { // pull the handle opposite to line segment, making node half-smooth @@ -1211,6 +1215,8 @@ void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::Nod bool p_is_line = sp_node_side_is_line(node, &node->p); bool n_is_line = sp_node_side_is_line(node, &node->n); +#define NODE_HAS_BOTH_HANDLES(node) ((Geom::L2(node->pos - node->n.pos) > 1e-6) && (Geom::L2(node->pos - node->p.pos) > 1e-6)) + if (p_has_handle && n_has_handle) { // do nothing, adjust_handles will line them up } else if (p_has_handle || n_has_handle) { @@ -1219,6 +1225,7 @@ void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::Nod Radial handle (node->pos - node->p.pos); if (fabs(line.a - handle.a) < 1e-3) { // lined up // already half-smooth; pull opposite handle too making it fully smooth + node->n.other->code = NR_CURVETO; node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3; } else { // do nothing, adjust_handles will line the handle up, producing a half-smooth node @@ -1228,6 +1235,7 @@ void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::Nod Radial handle (node->pos - node->n.pos); if (fabs(line.a - handle.a) < 1e-3) { // lined up // already half-smooth; pull opposite handle too making it fully smooth + node->code = NR_CURVETO; node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3; } else { // do nothing, adjust_handles will line the handle up, producing a half-smooth node @@ -1248,9 +1256,13 @@ void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::Nod node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos); } } else if (!p_has_handle && !n_has_handle) { - if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) { - // no handles, but both segments are either lnes or curves: - //pull both handles + if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other) || + (n_is_line && node->p.other && node->p.other->selected && NODE_HAS_BOTH_HANDLES(node->p.other)) || + (p_is_line && node->n.other && node->n.other->selected && NODE_HAS_BOTH_HANDLES(node->n.other)) + ) { + // no handles, but: both segments are either lines or curves; or: one is line and the + // node at the other side is selected (so it was just smoothed too!) and now has both + // handles: then pull both handles here // convert both to curves: node->code = NR_CURVETO; @@ -1305,7 +1317,7 @@ void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p) Inkscape::NodePath::Node *node_n = NULL; if (node->p.other) { - if (node->code == NR_LINETO) { + if (sp_node_side_is_line(node, &node->p)) { sp_node_adjust_handle(node, 1); sp_node_adjust_handle(node->p.other, -1); node_p = node->p.other; @@ -1316,7 +1328,7 @@ void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p) } } if (node->n.other) { - if (node->n.other->code == NR_LINETO) { + if (sp_node_side_is_line(node, &node->n)) { sp_node_adjust_handle(node, -1); sp_node_adjust_handle(node->n.other, 1); node_n = node->n.other; @@ -3590,7 +3602,6 @@ static void node_grabbed(SPKnot *knot, guint state, gpointer data) } n->is_dragging = true; - //sp_event_context_snap_window_open(n->subpath->nodepath->desktop->canvas); // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping) n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin; @@ -3608,7 +3619,6 @@ static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data) n->dragging_out = NULL; n->is_dragging = false; - //sp_event_context_snap_window_closed(n->subpath->nodepath->desktop->canvas); n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE); sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas); diff --git a/src/object-edit.cpp b/src/object-edit.cpp index ac62a12db..6b83413e4 100644 --- a/src/object-edit.cpp +++ b/src/object-edit.cpp @@ -930,7 +930,7 @@ ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderRelea _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"), SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); entity_start->create(desktop, item, this, - _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>" + _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> " "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"), SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR); entity_end->create(desktop, item, this, @@ -1111,7 +1111,7 @@ public: * [control] constrain inner arg to round per PI/4 */ void -SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state) +SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); @@ -1121,10 +1121,16 @@ SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/ gdouble dx = p[Geom::X] - spiral->cx; gdouble dy = p[Geom::Y] - spiral->cy; + gdouble moved_y = p[Geom::Y] - origin[Geom::Y]; + if (state & GDK_MOD1_MASK) { // adjust divergence by vertical drag, relative to rad - double new_exp = (spiral->rad + dy)/(spiral->rad); - spiral->exp = new_exp > 0? new_exp : 0; + if (spiral->rad > 0) { + double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down + spiral->exp += exp_delta; + if (spiral->exp < 1e-3) + spiral->exp = 1e-3; + } } else { // roll/unroll from inside gdouble arg_t0; diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 5dd9350dc..22d438c1e 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -161,8 +161,8 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, // This item is within snapping range, so record it as a candidate _candidates->push_back(SnapCandidate(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; + //SPObject *obj = (SPObject*)item; + //std::cout << "Snap candidate added: " << obj->id << std::endl; } } } @@ -296,6 +296,13 @@ void Inkscape::ObjectSnapper::_snapTranslatingGuideToNodes(SnappedConstraints &s // Iterate through all nodes, find out which one is the closest to this guide, and snap to it! _collectNodes(t, true); + // 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 + } + SnappedPoint s; Geom::Coord tol = getSnapperTolerance(); @@ -547,7 +554,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 = c.projection(p); + Geom::Point const p_proj_on_cl = p; // 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); @@ -683,14 +690,14 @@ 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); - // _snapRotatingGuideToNodes has not been implemented yet. + } // This method is used to snap the origin of a guide to nodes/paths, while dragging the origin along the guide void Inkscape::ObjectSnapper::guideConstrainedSnap(SnappedConstraints &sc, Geom::Point const &p, Geom::Point const &guide_normal, - ConstraintLine const &c) const + ConstraintLine const &/*c*/) const { /* Get a list of all the SPItems that we will try to snap to */ std::vector<SPItem*> cand; @@ -707,7 +714,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); - // _snapRotatingGuideToNodes has not been implemented yet. + } /** @@ -734,7 +741,8 @@ bool Inkscape::ObjectSnapper::GuidesMightSnap() const // almost the same as This || (_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.getIncludeItemCenter()) + || (_snapmanager->snapprefs.getSnapModeNode() && (_snapmanager->snapprefs.getSnapToItemPath() && _snapmanager->snapprefs.getSnapIntersectionCS())); return (_snap_enabled && _snapmanager->snapprefs.getSnapModeGuide() && snap_to_something); } diff --git a/src/pen-context.cpp b/src/pen-context.cpp index 261c7d4cf..144717332 100644 --- a/src/pen-context.cpp +++ b/src/pen-context.cpp @@ -263,7 +263,7 @@ sp_pen_context_finish(SPEventContext *ec) { SPPenContext *pc = SP_PEN_CONTEXT(ec); - sp_event_context_snap_window_closed(ec, false); //TODO: Detailed implementation of the snap window; now it's simply always open + sp_event_context_discard_delayed_snap_event(ec); if (pc->npoints != 0) { pen_cancel (pc); @@ -367,8 +367,6 @@ sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event) { SPPenContext *const pc = SP_PEN_CONTEXT(ec); - sp_event_context_snap_window_open(ec, false); //TODO: Detailed implementation of the snap window; now it's simply always open - gint ret = FALSE; switch (event->type) { @@ -540,9 +538,6 @@ static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const p = event_dt; spdc_endpoint_snap(pc, p, bevent.state); /* Snap node only if not hitting anchor. */ spdc_pen_set_subsequent_point(pc, p, true); - if (pc->polylines_only) { - spdc_pen_finish_segment(pc, p, bevent.state); - } } } @@ -786,10 +781,8 @@ pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent) switch (pc->state) { case SP_PEN_CONTEXT_POINT: case SP_PEN_CONTEXT_CONTROL: - if (!pc->polylines_only) { - spdc_endpoint_snap(pc, p, revent.state); - spdc_pen_finish_segment(pc, p, revent.state); - } + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); break; case SP_PEN_CONTEXT_CLOSE: spdc_endpoint_snap(pc, p, revent.state); diff --git a/src/pencil-context.cpp b/src/pencil-context.cpp index 4b87e86b5..d6050ba04 100644 --- a/src/pencil-context.cpp +++ b/src/pencil-context.cpp @@ -255,8 +255,15 @@ pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton const &beve /* Set first point of sequence */ SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop); - sp_event_context_snap_window_open(event_context); + if (bevent.state & GDK_CONTROL_MASK) { + if (!(bevent.state & GDK_SHIFT_MASK)) { + m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE); + } + spdc_create_single_dot(event_context, p, "/tools/freehand/pencil", bevent.state); + ret = true; + break; + } if (anchor) { p = anchor->dp; desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); @@ -349,7 +356,7 @@ pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mev /* We may be idle or already freehand */ if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) { if (pc->state == SP_PENCIL_CONTEXT_IDLE) { - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); } pc->state = SP_PENCIL_CONTEXT_FREEHAND; @@ -416,8 +423,10 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re case SP_PENCIL_CONTEXT_IDLE: /* Releasing button in idle mode means single click */ /* We have already set up start point/anchor in button_press */ - pc->state = SP_PENCIL_CONTEXT_ADDLINE; - //sp_event_context_snap_window_open(dt->canvas); + if (!(revent.state & GDK_CONTROL_MASK)) { + // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed + pc->state = SP_PENCIL_CONTEXT_ADDLINE; + } ret = TRUE; break; case SP_PENCIL_CONTEXT_ADDLINE: @@ -431,7 +440,7 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re spdc_set_endpoint(pc, p); spdc_finish_endpoint(pc); pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); ret = TRUE; break; case SP_PENCIL_CONTEXT_FREEHAND: @@ -445,7 +454,6 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re } pc->state = SP_PENCIL_CONTEXT_SKETCH; - //sp_event_context_snap_window_open(dt->canvas); } else { /* Finish segment now */ /// \todo fixme: Clean up what follows (Lauris) @@ -465,7 +473,6 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); } pc->state = SP_PENCIL_CONTEXT_IDLE; - // sp_event_context_snap_window_closed(dt->canvas); // reset sketch mode too pc->sketch_n = 0; } @@ -498,7 +505,7 @@ pencil_cancel (SPPencilContext *const pc) pc->is_drawing = false; pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(pc)); + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); pc->red_curve->reset(); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); @@ -589,7 +596,7 @@ pencil_handle_key_release(SPPencilContext *const pc, guint const keyval, guint c pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); } pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(pc)); + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand sketch")); ret = TRUE; } diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 21bacd5a4..6185ff729 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -3,6 +3,11 @@ #include <inkscape-version.h> +#ifdef N_ +#undef N_ +#endif +#define N_(x) x + /* The root's "version" attribute describes the preferences file format version. * It should only increase when a backwards-incompatible change is made, * and special handling has to be added to the preferences class to update @@ -90,12 +95,12 @@ static char const preferences_skeleton[] = " mass=\"2\" angle=\"30\" width=\"15\" thinning=\"10\" flatness=\"90\" cap_rounding=\"0.0\" usecurrent=\"1\"\n" " tracebackground=\"0\" usepressure=\"1\" usetilt=\"0\" keep_selected=\"1\">\n" " <group id=\"preset\">\n" -" <group id=\"cp0\" name=\"Dip pen\" mass=\"2\" wiggle=\"0.0\" angle=\"30.0\" thinning=\"10\" tremor=\"0.0\" flatness=\"90\" cap_rounding=\"0.0\" tracebackground=\"0\" usepressure=\"1\" usetilt=\"1\" />\n" -" <group id=\"cp1\" name=\"Marker\" mass=\"2\" wiggle=\"0.0\" angle=\"90.0\" thinning=\"0.0\" tremor=\"0.0\" flatness=\"0.0\" cap_rounding=\"1.0\" tracebackground=\"0\" usepressure=\"0\" usetilt=\"0\" />\n" -" <group id=\"cp2\" name=\"Brush\" mass=\"2\" wiggle=\"25\" angle=\"45.0\" thinning=\"-40\" tremor=\"0.0\" flatness=\"16\" cap_rounding=\".1\" tracebackground=\"0\" usepressure=\"1\" usetilt=\"1\" />\n" -" <group id=\"cp3\" name=\"Wiggly\" usetilt=\"1\" tracebackground=\"0\" usepressure=\"1\" cap_rounding=\"0.1\" flatness=\"16\" tremor=\"18\" thinning=\"-30\" angle=\"30\" wiggle=\"50\" mass=\"0\" />\n" -" <group id=\"cp4\" name=\"Splotchy\" width=\"100\" usetilt=\"1\" tracebackground=\"0\" usepressure=\"0\" cap_rounding=\"1\" flatness=\"0\" tremor=\"10\" thinning=\"30\" angle=\"30\" wiggle=\"0\" mass=\"0\" />\n" -" <group id=\"cp5\" name=\"Tracing\" width=\"50\" mass=\"0\" wiggle=\"0.0\" angle=\"0.0\" thinning=\"0.0\" tremor=\"0.0\" flatness=\"0\" cap_rounding=\"0.0\" tracebackground=\"1\" usepressure=\"1\" usetilt=\"1\"/>\n" +" <group id=\"cp0\" name=\"" N_("Dip pen") "\" mass=\"2\" wiggle=\"0.0\" angle=\"30.0\" thinning=\"10\" tremor=\"0.0\" flatness=\"90\" cap_rounding=\"0.0\" tracebackground=\"0\" usepressure=\"1\" usetilt=\"1\" />\n" +" <group id=\"cp1\" name=\"" N_("Marker") "\" mass=\"2\" wiggle=\"0.0\" angle=\"90.0\" thinning=\"0.0\" tremor=\"0.0\" flatness=\"0.0\" cap_rounding=\"1.0\" tracebackground=\"0\" usepressure=\"0\" usetilt=\"0\" />\n" +" <group id=\"cp2\" name=\"" N_("Brush") "\" mass=\"2\" wiggle=\"25\" angle=\"45.0\" thinning=\"-40\" tremor=\"0.0\" flatness=\"16\" cap_rounding=\".1\" tracebackground=\"0\" usepressure=\"1\" usetilt=\"1\" />\n" +" <group id=\"cp3\" name=\"" N_("Wiggly") "\" usetilt=\"1\" tracebackground=\"0\" usepressure=\"1\" cap_rounding=\"0.1\" flatness=\"16\" tremor=\"18\" thinning=\"-30\" angle=\"30\" wiggle=\"50\" mass=\"0\" />\n" +" <group id=\"cp4\" name=\"" N_("Splotchy") "\" width=\"100\" usetilt=\"1\" tracebackground=\"0\" usepressure=\"0\" cap_rounding=\"1\" flatness=\"0\" tremor=\"10\" thinning=\"30\" angle=\"30\" wiggle=\"0\" mass=\"0\" />\n" +" <group id=\"cp5\" name=\"" N_("Tracing") "\" width=\"50\" mass=\"0\" wiggle=\"0.0\" angle=\"0.0\" thinning=\"0.0\" tremor=\"0.0\" flatness=\"0\" cap_rounding=\"0.0\" tracebackground=\"1\" usepressure=\"1\" usetilt=\"1\"/>\n" " </group>\n" " </eventcontext>\n" " <eventcontext id=\"eraser\" mode=\"0\" style=\"fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;\"\n" @@ -179,11 +184,12 @@ static char const preferences_skeleton[] = " <group id=\"preferences\" state=\"1\"/>\n" " <group id=\"gradienteditor\"/>\n" " <group id=\"object\"/>\n" -" <group id=\"export\">\n" +" <group id=\"export\" default=\"\" append_extension=\"1\" path=\"\">\n" " <group id=\"exportarea\"/>\n" " <group id=\"defaultxdpi\"/>\n" " </group>\n" " <group id=\"save_as\" default=\"\" append_extension=\"1\" enable_preview=\"1\" path=\"\"/>\n" +" <group id=\"save_copy\" default=\"\" append_extension=\"1\" enable_preview=\"1\" path=\"\"/>\n" " <group id=\"open\" enable_preview=\"1\" path=\"\"/>\n" " <group id=\"import\" enable_preview=\"1\" path=\"\"/>\n" " <group id=\"debug\" redirect=\"0\"/>\n" diff --git a/src/preferences.cpp b/src/preferences.cpp index cb71e03b2..39a9e4d69 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -15,6 +15,7 @@ #include <glibmm/i18n.h> #include <glib.h> #include <glib/gstdio.h> +#include <gtk/gtk.h> #include "preferences.h" #include "preferences-skeleton.h" #include "inkscape.h" @@ -26,6 +27,34 @@ namespace Inkscape { +static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg ); +static void migrateDetails( Inkscape::XML::Document *from, Inkscape::XML::Document *to ); + +static Inkscape::XML::Document *migrateFromDoc = 0; + +// TODO clean up. Function copied from file.cpp: +// what gets passed here is not actually an URI... it is an UTF-8 encoded filename (!) +static void file_add_recent(gchar const *uri) +{ + if (!uri) { + g_warning("file_add_recent: uri == NULL"); + } else { + GtkRecentManager *recent = gtk_recent_manager_get_default(); + gchar *fn = g_filename_from_utf8(uri, -1, NULL, NULL, NULL); + if (fn) { + if (g_file_test(fn, G_FILE_TEST_EXISTS)) { + gchar *uriToAdd = g_filename_to_uri(fn, NULL, NULL); + if (uriToAdd) { + gtk_recent_manager_add_item(recent, uriToAdd); + g_free(uriToAdd); + } + } + g_free(fn); + } + } +} + + // private inner class definition /** @@ -47,7 +76,6 @@ private: Glib::ustring const _filter; }; - Preferences::Preferences() : _prefs_basename(PREFERENCES_FILE_NAME), _prefs_dir(""), @@ -62,7 +90,7 @@ Preferences::Preferences() : _prefs_dir = path; g_free(path); - path = profile_path(_prefs_basename.data()); + path = profile_path(_prefs_basename.c_str()); _prefs_filename = path; g_free(path); @@ -104,17 +132,17 @@ void Preferences::_load() // NOTE: After we upgrade to Glib 2.16, use Glib::ustring::compose // 1. Does the file exist? - if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_EXISTS)) { + if (!g_file_test(_prefs_filename.c_str(), G_FILE_TEST_EXISTS)) { // No - we need to create one. // Does the profile directory exist? - if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_EXISTS)) { + if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_EXISTS)) { // No - create the profile directory - if (g_mkdir(_prefs_dir.data(), 0755)) { + if (g_mkdir(_prefs_dir.c_str(), 0755)) { // the creation failed //_reportError(Glib::ustring::compose(_("Cannot create profile directory %1."), // Glib::filename_to_utf8(_prefs_dir)), not_saved); gchar *msg = g_strdup_printf(_("Cannot create profile directory %s."), - Glib::filename_to_utf8(_prefs_dir).data()); + Glib::filename_to_utf8(_prefs_dir).c_str()); _reportError(msg, not_saved); g_free(msg); return; @@ -127,28 +155,32 @@ void Preferences::_load() g_free(dir); } - } else if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_IS_DIR)) { + } else if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_IS_DIR)) { // The profile dir is not actually a directory //_reportError(Glib::ustring::compose(_("%1 is not a valid directory."), // Glib::filename_to_utf8(_prefs_dir)), not_saved); gchar *msg = g_strdup_printf(_("%s is not a valid directory."), - Glib::filename_to_utf8(_prefs_dir).data()); + Glib::filename_to_utf8(_prefs_dir).c_str()); _reportError(msg, not_saved); g_free(msg); return; } // The profile dir exists and is valid. - if (!g_file_set_contents(_prefs_filename.data(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) { + if (!g_file_set_contents(_prefs_filename.c_str(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) { // The write failed. //_reportError(Glib::ustring::compose(_("Failed to create the preferences file %1."), // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("Failed to create the preferences file %s."), - Glib::filename_to_utf8(_prefs_filename).data()); + Glib::filename_to_utf8(_prefs_filename).c_str()); _reportError(msg, not_saved); g_free(msg); return; } + if ( migrateFromDoc ) { + migrateDetails( migrateFromDoc, _prefs_doc ); + } + // The prefs file was just created. // We can return now and skip the rest of the load process. _writable = true; @@ -156,58 +188,69 @@ void Preferences::_load() } // Yes, the pref file exists. + Glib::ustring errMsg; + Inkscape::XML::Document *prefs_read = loadImpl( _prefs_filename, errMsg ); + + if ( prefs_read ) { + // Merge the loaded prefs with defaults. + _prefs_doc->root()->mergeFrom(prefs_read->root(), "id"); + Inkscape::GC::release(prefs_read); + _writable = true; + } else { + _reportError(errMsg, not_saved); + } +} + +//_reportError(msg, not_saved); +static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg ) +{ // 2. Is it a regular file? - if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_IS_REGULAR)) { - //_reportError(Glib::ustring::compose(_("The preferences file %1 is not a regular file."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); + if (!g_file_test(prefsFilename.c_str(), G_FILE_TEST_IS_REGULAR)) { gchar *msg = g_strdup_printf(_("The preferences file %s is not a regular file."), - Glib::filename_to_utf8(_prefs_filename).data()); - _reportError(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } // 3. Is the file readable? gchar *prefs_xml = NULL; gsize len = 0; - if (!g_file_get_contents(_prefs_filename.data(), &prefs_xml, &len, NULL)) { - //_reportError(Glib::ustring::compose(_("The preferences file %1 could not be read."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); + if (!g_file_get_contents(prefsFilename.c_str(), &prefs_xml, &len, NULL)) { gchar *msg = g_strdup_printf(_("The preferences file %s could not be read."), - Glib::filename_to_utf8(_prefs_filename).data()); - _reportError(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } // 4. Is it valid XML? Inkscape::XML::Document *prefs_read = sp_repr_read_mem(prefs_xml, len, NULL); g_free(prefs_xml); if (!prefs_read) { - //_reportError(Glib::ustring::compose(_("The preferences file %1 is not a valid XML document."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("The preferences file %s is not a valid XML document."), - Glib::filename_to_utf8(_prefs_filename).data()); - _reportError(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); - return; + return 0; } // 5. Basic sanity check: does the root element have a correct name? if (strcmp(prefs_read->root()->name(), "inkscape")) { - //_reportError(Glib::ustring::compose(_("The file %1 is not a valid Inkscape preferences file."), - // Glib::filename_to_utf8(_prefs_filename)), not_saved); gchar *msg = g_strdup_printf(_("The file %s is not a valid Inkscape preferences file."), - Glib::filename_to_utf8(_prefs_filename).data()); - _reportError(msg, not_saved); + Glib::filename_to_utf8(prefsFilename).c_str()); + errMsg = msg; g_free(msg); Inkscape::GC::release(prefs_read); - return; + return 0; } - // Merge the loaded prefs with defaults. - _prefs_doc->root()->mergeFrom(prefs_read->root(), "id"); - Inkscape::GC::release(prefs_read); - _writable = true; + return prefs_read; +} + +static void migrateDetails( Inkscape::XML::Document *from, Inkscape::XML::Document *to ) +{ + // TODO pull in additional prefs with more granularity + to->root()->mergeFrom(from->root(), "id"); } /** @@ -231,7 +274,7 @@ void Preferences::save() // There are many other factors, so ask if you would like to learn them. - JAC Glib::ustring utf8name = Glib::filename_to_utf8(_prefs_filename); if (!utf8name.empty()) { - sp_repr_save_file(_prefs_doc, utf8name.data()); + sp_repr_save_file(_prefs_doc, utf8name.c_str()); } } } @@ -252,6 +295,71 @@ bool Preferences::getLastError( Glib::ustring& primary, Glib::ustring& secondary return result; } +void Preferences::migrate( std::string const& legacyDir, std::string const& prefdir ) +{ + int mode = S_IRWXU; +#ifdef S_IRGRP + mode |= S_IRGRP; +#endif +#ifdef S_IXGRP + mode |= S_IXGRP; +#endif +#ifdef S_IXOTH + mode |= S_IXOTH; +#endif + if ( g_mkdir_with_parents(prefdir.c_str(), mode) == -1 ) { + } else { + } + + gchar * oldPrefFile = g_build_filename(legacyDir.c_str(), PREFERENCES_FILE_NAME, NULL); + if (oldPrefFile) { + if (g_file_test(oldPrefFile, G_FILE_TEST_EXISTS)) { + Glib::ustring errMsg; + Inkscape::XML::Document *oldPrefs = loadImpl( oldPrefFile, errMsg ); + if (oldPrefs) { + Glib::ustring docId("documents"); + Glib::ustring recentId("recent"); + Inkscape::XML::Node *node = oldPrefs->root(); + Inkscape::XML::Node *child = 0; + Inkscape::XML::Node *recentNode = 0; + if (node->attribute("version")) { + node->setAttribute("version", "1"); + } + for (child = node->firstChild(); child; child = child->next()) { + if (docId == child->attribute("id")) { + for (child = child->firstChild(); child; child = child->next()) { + if (recentId == child->attribute("id")) { + recentNode = child; + for (child = child->firstChild(); child; child = child->next()) { + gchar const* uri = child->attribute("uri"); + if (uri) { + file_add_recent(uri); + } + } + break; + } + } + break; + } + } + + if (recentNode) { + while (recentNode->firstChild()) { + recentNode->removeChild(recentNode->firstChild()); + } + } + migrateFromDoc = oldPrefs; + //Inkscape::GC::release(oldPrefs); + oldPrefs = 0; + } else { + g_warning( "%s", errMsg.c_str() ); + } + } + g_free(oldPrefFile); + oldPrefFile = 0; + } +} + // Now for the meat. /** @@ -345,7 +453,7 @@ void Preferences::setDouble(Glib::ustring const &pref_path, double value) */ void Preferences::setString(Glib::ustring const &pref_path, Glib::ustring const &value) { - _setRawValue(pref_path, value.data()); + _setRawValue(pref_path, value.c_str()); } void Preferences::setStyle(Glib::ustring const &pref_path, SPCSSAttr *style) @@ -515,7 +623,7 @@ Inkscape::XML::Node *Preferences::_getNode(Glib::ustring const &pref_key, bool c Inkscape::XML::Node *node = _prefs_doc->root(); Inkscape::XML::Node *child = NULL; - gchar **splits = g_strsplit(pref_key.data(), "/", 0); + gchar **splits = g_strsplit(pref_key.c_str(), "/", 0); if ( splits ) { for (int part_i = 0; splits[part_i]; ++part_i) { @@ -568,7 +676,7 @@ void Preferences::_getRawValue(Glib::ustring const &path, gchar const *&result) if ( node == NULL ) { result = NULL; } else { - gchar const *attr = node->attribute(attr_key.data()); + gchar const *attr = node->attribute(attr_key.c_str()); if ( attr == NULL ) { result = NULL; } else { @@ -585,7 +693,7 @@ void Preferences::_setRawValue(Glib::ustring const &path, gchar const *value) // set the attribute Inkscape::XML::Node *node = _getNode(node_key, true); - node->setAttribute(attr_key.data(), value); + node->setAttribute(attr_key.c_str(), value); } // The _extract* methods are where the actual wrok is done - they define how preferences are stored @@ -640,7 +748,7 @@ SPCSSAttr *Preferences::_extractInheritedStyle(Entry const &v) _keySplit(v._pref_path, node_key, attr_key); Inkscape::XML::Node *node = _getNode(node_key, false); - return sp_repr_css_attr_inherited(node, attr_key.data()); + return sp_repr_css_attr_inherited(node, attr_key.c_str()); } // XML backend helper: Split the path into a node key and an attribute key. diff --git a/src/preferences.h b/src/preferences.h index d8706a501..a7be08009 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -420,6 +420,12 @@ public: * @{ */ + + /** + * Copies values from old location to new. + */ + static void migrate( std::string const& legacyDir, std::string const& prefdir ); + /** * @brief Access the singleton Preferences object. */ diff --git a/src/rdf.cpp b/src/rdf.cpp index f0b174922..0a8fd51f6 100644 --- a/src/rdf.cpp +++ b/src/rdf.cpp @@ -205,7 +205,7 @@ struct rdf_license_t rdf_licenses [] = { }, { N_("FreeArt"), - "http://artlibre.org/licence.php/lalgb.html", + "http://artlibre.org/licence/lal", rdf_license_freeart, }, diff --git a/src/rect-context.cpp b/src/rect-context.cpp index 51b5f2e85..b88b4b83a 100644 --- a/src/rect-context.cpp +++ b/src/rect-context.cpp @@ -50,6 +50,7 @@ static void sp_rect_context_init(SPRectContext *rect_context); static void sp_rect_context_dispose(GObject *object); static void sp_rect_context_setup(SPEventContext *ec); +static void sp_rect_context_finish(SPEventContext *ec); static void sp_rect_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val); static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent *event); @@ -91,6 +92,7 @@ static void sp_rect_context_class_init(SPRectContextClass *klass) object_class->dispose = sp_rect_context_dispose; event_context_class->setup = sp_rect_context_setup; + event_context_class->finish = sp_rect_context_finish; event_context_class->set = sp_rect_context_set; event_context_class->root_handler = sp_rect_context_root_handler; event_context_class->item_handler = sp_rect_context_item_handler; @@ -117,6 +119,21 @@ static void sp_rect_context_init(SPRectContext *rect_context) new (&rect_context->sel_changed_connection) sigc::connection(); } +static void sp_rect_context_finish(SPEventContext *ec) +{ + SPRectContext *rc = SP_RECT_CONTEXT(ec); + SPDesktop *desktop = ec->desktop; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + sp_rect_finish(rc); + rc->sel_changed_connection.disconnect(); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } +} + + static void sp_rect_context_dispose(GObject *object) { SPRectContext *rc = SP_RECT_CONTEXT(object); @@ -258,7 +275,6 @@ static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); dragging = true; - sp_event_context_snap_window_open(event_context); /* Position center */ Geom::Point button_dt(desktop->w2d(button_w)); @@ -307,7 +323,7 @@ static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent event_context->xp = event_context->yp = 0; if (event->button.button == 1 && !event_context->space_panning) { dragging = false; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the rect @@ -375,7 +391,7 @@ static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent case GDK_Escape: if (dragging) { dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); // if drawing, cancel, otherwise pass it up for deselecting sp_rect_cancel(rc); ret = TRUE; @@ -387,7 +403,7 @@ static gint sp_rect_context_root_handler(SPEventContext *event_context, GdkEvent sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the rect sp_rect_finish(rc); diff --git a/src/select-context.cpp b/src/select-context.cpp index f58c034bd..606934ca6 100644 --- a/src/select-context.cpp +++ b/src/select-context.cpp @@ -212,7 +212,7 @@ sp_select_context_abort(SPEventContext *event_context) seltrans->ungrab(); sc->moved = FALSE; sc->dragging = FALSE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); drag_escaped = 1; if (sc->item) { @@ -324,7 +324,6 @@ sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkE // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag } else { sc->dragging = TRUE; - sp_event_context_snap_window_open(event_context); sc->moved = FALSE; sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5); @@ -424,7 +423,7 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item)); sp_desktop_selection(desktop)->clear(); sc->dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); sp_canvas_end_forced_full_redraws(desktop->canvas); } else { // switch tool @@ -495,9 +494,6 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (sc->button_press_ctrl || (sc->button_press_alt && !sc->button_press_shift && !selection->isEmpty())) { // if it's not click and ctrl or alt was pressed (the latter with some selection // but not with shift) we want to drag rather than rubberband - if (sc->dragging == FALSE) { - sp_event_context_snap_window_open(event_context); - } sc->dragging = TRUE; sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5); @@ -539,7 +535,7 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (item_at_point && !selection->includes(item_at_point)) selection->set(item_at_point); } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible - seltrans->grab(p, -1, -1, FALSE); + seltrans->grab(p, -1, -1, FALSE, TRUE); sc->moved = TRUE; } if (!seltrans->isEmpty()) @@ -549,7 +545,7 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) ret = TRUE; } else { sc->dragging = FALSE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); sp_canvas_end_forced_full_redraws(desktop->canvas); } } else { @@ -598,7 +594,7 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) } } sc->dragging = FALSE; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); sp_canvas_end_forced_full_redraws(desktop->canvas); if (sc->item) { @@ -952,8 +948,9 @@ sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event) } } // set cursor to default. - if (!desktop->isWaitingCursor()) + if (!desktop->isWaitingCursor()) { gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, event_context->cursor); + } break; default: break; diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 3bd5f611a..8ff00d60a 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -241,8 +241,10 @@ void Inkscape::SelTrans::setCenter(Geom::Point const &p) _updateHandles(); } -void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles) +void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles, bool translating) { + // While dragging a handle, we will either scale, skew, or rotate and the "translating" parameter will be false + // When dragging the selected item itself however, we will translate the selection and that parameter will be true Inkscape::Selection *selection = sp_desktop_selection(_desktop); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -273,10 +275,11 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s // The selector tool should snap the bbox, special snappoints, and path nodes // (The special points are the handles, center, rotation axis, font baseline, ends of spiral, etc.) - // First, determine the bounding box for snapping ... + // First, determine the bounding box _bbox = selection->bounds(_snap_bbox_type); _approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX); // Used for correctly scaling the strokewidth _geometric_bbox = selection->bounds(SPItem::GEOMETRIC_BBOX); + _point = p; if (_geometric_bbox) { _point_geom = _geometric_bbox->min() + _geometric_bbox->dimensions() * Geom::Scale(x, y); @@ -286,31 +289,31 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s // Next, get all points to consider for snapping SnapManager const &m = _desktop->namedview->snap_manager; - Inkscape::SnapPreferences local_snapprefs = m.snapprefs; - local_snapprefs.setSnapToItemNode(true); // We should get at least the cusp nodes here. This might - // have been turned off because (for example) the user only want paths as a snap target, not nodes - // but as a snap source we still need some nodes though! + Inkscape::SnapPreferences local_snapprefs = m.snapprefs; + local_snapprefs.setSnapToItemNode(true); // We should get at least the cusp nodes here. This might + // have been turned off because (for example) the user only want paths as a snap target, not nodes + // 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); - if (_snap_points.size() > 100) { - /* 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 - (s)he could hardly discern which node would be snapping */ - if (prefs->getBool("/options/snapclosestonly/value", false)) { - _keepClosestPointOnly(_snap_points, p); - } else { - _snap_points = snap_points_hull; - } - // Unfortunately, by now we will have lost the font-baseline snappoints :-( - } + _snap_points = selection->getSnapPoints(&local_snapprefs); + std::vector<std::pair<Geom::Point, int> > 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 + (s)he could hardly discern which node would be snapping */ + if (prefs->getBool("/options/snapclosestonly/value", false)) { + _keepClosestPointOnly(_snap_points, p); + } else { + _snap_points = snap_points_hull; + } + // Unfortunately, by now we will have lost the font-baseline snappoints :-( + } // Find bbox hulling all special points, which excludes stroke width. Here we need to include the // path nodes, for example because a rectangle which has been converted to a path doesn't have // 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(); + std::vector<std::pair<Geom::Point, int> >::iterator i = snap_points_hull.begin(); snap_points_bbox = Geom::Rect((*i).first, (*i).first); i++; while (i != snap_points_hull.end()) { @@ -320,11 +323,29 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s } _bbox_points.clear(); + _bbox_points_for_translating.clear(); + // Collect the bounding box's corners and midpoints for each selected item + if (m.snapprefs.getSnapModeBBox()) { + bool mp = m.snapprefs.getSnapBBoxMidpoints(); + bool emp = m.snapprefs.getSnapBBoxEdgeMidpoints(); + // Preferably we'd use the bbox of each selected item, instead of the bbox of the selection as a whole; for translations + // this is easy to do, but when snapping the visual bbox while scaling we will have to compensate for the scaling of the + // stroke width. (see get_scale_transform_with_stroke()). This however is currently only implemented for a single bbox. + // That's why we have both _bbox_points_for_translating and _bbox_points. + getBBoxPoints(selection->bounds(_snap_bbox_type), &_bbox_points, false, true, emp, mp); + if (((_items.size() > 0) && (_items.size() < 50)) || prefs->getBool("/options/snapclosestonly/value", false)) { + // More than 50 items will produce at least 200 bbox points, which might make Inkscape crawl + // (see the comment a few lines above). In that case we will use the bbox of the selection as a whole + for (unsigned i = 0; i < _items.size(); i++) { + getBBoxPoints(sp_item_bbox_desktop(_items[i], _snap_bbox_type), &_bbox_points_for_translating, false, true, emp, mp); + } + } else { + _bbox_points_for_translating = _bbox_points; // use the bbox points of the selection as a whole + } + } + if (_bbox) { - if (m.snapprefs.getSnapModeBBox()) { - getBBoxPoints(_bbox, &_bbox_points, false, true, m.snapprefs.getSnapBBoxEdgeMidpoints(), m.snapprefs.getSnapBBoxMidpoints()); - } - // There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged): + // There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged): // - one for snapping the boundingbox, which can be either visual or geometric // - one for snapping the special points // The "opposite" in case of a geometric boundingbox always coincides with the "opposite" for the special points @@ -339,44 +360,67 @@ void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool s // (i.e. when weight == 1), then we will not even try to snap to other points and discard those other // points immediately. - if (prefs->getBool("/options/snapclosestonly/value", false)) { - if (m.snapprefs.getSnapModeNode()) { - _keepClosestPointOnly(_snap_points, p); - } else { - _snap_points.clear(); // don't keep any point - } - - if (m.snapprefs.getSnapModeBBox()) { - _keepClosestPointOnly(_bbox_points, p); - } else { - _bbox_points.clear(); // don't keep any point - } - - g_assert(_bbox_points.size() < 2 && _snap_points.size() < 2); - if (_snap_points.size() == 1 && _bbox_points.size() == 1) { //both vectors can only have either one or zero elements - // So we have exactly one bbox corner and one node left; now find out which is closest and delete the other one - if (Geom::L2((_snap_points.at(0)).first - p) < Geom::L2((_bbox_points.at(0)).first - p)) { - _bbox_points.clear(); - } else { - _snap_points.clear(); - } - } - - // Optionally, show the snap source - if (!(_state == STATE_ROTATE && x != 0.5 && y != 0.5)) { // but not when we're draging a rotation handle, because that won't snap - // Now either _bbox_points or _snap_points has a single element, the other one has zero..... or both have zero elements - g_assert((_bbox_points.size() + _snap_points.size()) < 2); - if (m.snapprefs.getSnapEnabledGlobally()) { - if (_bbox_points.size() == 1) { - _desktop->snapindicator->set_new_snapsource(_bbox_points.at(0)); - } else if (_snap_points.size() == 1){ - _desktop->snapindicator->set_new_snapsource(_snap_points.at(0)); - } - } - } - } - - //sp_event_context_snap_window_open(_desktop->event_context); + if (prefs->getBool("/options/snapclosestonly/value", false)) { + if (m.snapprefs.getSnapModeNode()) { + _keepClosestPointOnly(_snap_points, p); + } else { + _snap_points.clear(); // don't keep any point + } + + if (m.snapprefs.getSnapModeBBox()) { + _keepClosestPointOnly(_bbox_points, p); + _keepClosestPointOnly(_bbox_points_for_translating, p); + } else { + _bbox_points.clear(); // don't keep any point + _bbox_points_for_translating.clear(); + } + + // Each of the three vectors of snappoints now contains either one snappoint or none at all. + if (_snap_points.size() > 1 || _bbox_points.size() > 1 || _bbox_points_for_translating.size() > 1) { + g_warning("Incorrect assumption encountered while finding the snap source; nothing serious, but please report to Diederik"); + } + + // 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; + + if (translating) { + _bbox_points.clear(); + if (dsp > dbbpft) { + _snap_points.clear(); + } else { + _bbox_points_for_translating.clear(); + } + } else { + _bbox_points_for_translating.clear(); + if (dsp > dbbp) { + _snap_points.clear(); + } else { + _bbox_points.clear(); + } + } + + if ((_snap_points.size() + _bbox_points.size() + _bbox_points_for_translating.size()) > 1) { + g_warning("Checking number of snap sources failed; nothing serious, but please report to Diederik"); + } + + // Optionally, show the snap source + if (!(_state == STATE_ROTATE && x != 0.5 && y != 0.5)) { // but not when we're dragging a rotation handle, because that won't snap + // Now either _bbox_points or _snap_points has a single element, the other one has zero..... or both have zero elements + if ((_snap_points.size() + _bbox_points.size() + _bbox_points_for_translating.size()) > 1) { + g_warning("too many snap sources to display, please fix this"); + } else if (m.snapprefs.getSnapEnabledGlobally()) { + if (_bbox_points.size() == 1) { + _desktop->snapindicator->set_new_snapsource(_bbox_points.at(0)); + } else if (_bbox_points_for_translating.size() == 1) { + _desktop->snapindicator->set_new_snapsource(_bbox_points_for_translating.at(0)); + } else if (_snap_points.size() == 1){ + _desktop->snapindicator->set_new_snapsource(_snap_points.at(0)); + } + } + } + } if ((x != -1) && (y != -1)) { sp_canvas_item_show(_norm); @@ -430,8 +474,6 @@ void Inkscape::SelTrans::ungrab() _grabbed = false; _show_handles = true; - //sp_event_context_snap_window_closed(_desktop->event_context); - _desktop->snapindicator->remove_snapsource(); Inkscape::Selection *selection = sp_desktop_selection(_desktop); @@ -484,10 +526,10 @@ void Inkscape::SelTrans::ungrab() if (_current_relative_affine.isTranslation()) { sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT, _("Move")); - } else if (_current_relative_affine.isScale()) { + } else if (_current_relative_affine.without_translation().isScale()) { sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT, _("Scale")); - } else if (_current_relative_affine.isRotation()) { + } else if (_current_relative_affine.without_translation().isRotation()) { sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT, _("Rotate")); } else { @@ -797,7 +839,7 @@ void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint /*state*/, SPSelTransHan break; } - grab(sp_knot_position(knot), handle.x, handle.y, FALSE); + grab(sp_knot_position(knot), handle.x, handle.y, FALSE, FALSE); } @@ -1401,17 +1443,17 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) ** FIXME: this will snap to more than just the grid, nowadays. */ - m.setup(_desktop, true, _items_const); - m.freeSnapReturnByRef(SnapPreferences::SNAPPOINT_NODE, dxy, Inkscape::SNAPSOURCE_UNDEFINED); + m.setup(_desktop, true, _items_const); + m.freeSnapReturnByRef(SnapPreferences::SNAPPOINT_NODE, dxy, Inkscape::SNAPSOURCE_UNDEFINED); } else if (shift) { - if (control) { // shift & control: constrained movement without snapping - if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) { - dxy[Geom::Y] = 0; - } else { - dxy[Geom::X] = 0; - } - } + if (control) { // shift & control: constrained movement without snapping + if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) { + dxy[Geom::Y] = 0; + } else { + dxy[Geom::X] = 0; + } + } } else { //!shift: with snapping /* We're snapping to things, possibly with a constraint to horizontal or @@ -1419,9 +1461,9 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) ** pick the smallest. */ - m.setup(_desktop, false, _items_const); + m.setup(_desktop, false, _items_const); - /* This will be our list of possible translations */ + /* This will be our list of possible translations */ std::list<Inkscape::SnappedPoint> s; if (control) { // constrained movement with snapping @@ -1429,36 +1471,36 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) /* Snap to things, and also constrain to horizontal or vertical movement */ unsigned int dim = fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y]) ? Geom::X : Geom::Y; - // When doing a constrained translation, all points will move in the same direction, i.e. - // either horizontally or vertically. Therefore we only have to specify the direction of - // the constraint-line once. The constraint lines are parallel, but might not be colinear. - // Therefore we will have to set the point through which the constraint-line runs - // individually for each point to be snapped; this will be handled however by _snapTransformed() - s.push_back(m.constrainedSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_BBOX, - _bbox_points, - _point, - Inkscape::Snapper::ConstraintLine(component_vectors[dim]), - dxy)); - - s.push_back(m.constrainedSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_NODE, - _snap_points, - _point, - Inkscape::Snapper::ConstraintLine(component_vectors[dim]), - dxy)); + // When doing a constrained translation, all points will move in the same direction, i.e. + // either horizontally or vertically. Therefore we only have to specify the direction of + // the constraint-line once. The constraint lines are parallel, but might not be colinear. + // Therefore we will have to set the point through which the constraint-line runs + // individually for each point to be snapped; this will be handled however by _snapTransformed() + s.push_back(m.constrainedSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_BBOX, + _bbox_points_for_translating, + _point, + Inkscape::Snapper::ConstraintLine(component_vectors[dim]), + dxy)); + + s.push_back(m.constrainedSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_NODE, + _snap_points, + _point, + Inkscape::Snapper::ConstraintLine(component_vectors[dim]), + dxy)); } else { // !control // Let's leave this timer code here for a while. I'll probably need it in the near future (Diederik van Lierop) /* GTimeVal starttime; GTimeVal endtime; - g_get_current_time(&starttime); */ + g_get_current_time(&starttime); */ /* Snap to things with no constraint */ - s.push_back(m.freeSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_BBOX, _bbox_points, _point, dxy)); - s.push_back(m.freeSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_NODE, _snap_points, _point, dxy)); + s.push_back(m.freeSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_BBOX, _bbox_points_for_translating, _point, dxy)); + s.push_back(m.freeSnapTranslation(Inkscape::SnapPreferences::SNAPPOINT_NODE, _snap_points, _point, dxy)); - /*g_get_current_time(&endtime); - double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0; - std::cout << "Time spent snapping: " << elapsed << std::endl; */ + /*g_get_current_time(&endtime); + double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0; + std::cout << "Time spent snapping: " << elapsed << std::endl; */ } /* Pick one */ @@ -1588,29 +1630,36 @@ Geom::Point Inkscape::SelTrans::_calcAbsAffineGeom(Geom::Scale const geom_scale) Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool const transform_stroke = prefs->getBool("/options/transform/stroke", true); - Geom::Rect visual_bbox = get_visual_bbox(_geometric_bbox, _absolute_affine, _strokewidth, transform_stroke); + if (_geometric_bbox) { + Geom::Rect visual_bbox = get_visual_bbox(_geometric_bbox, _absolute_affine, _strokewidth, transform_stroke); + // return the new handle position + return visual_bbox.min() + visual_bbox.dimensions() * Geom::Scale(_handle_x, _handle_y); + } - // return the new handle position - return visual_bbox.min() + visual_bbox.dimensions() * Geom::Scale(_handle_x, _handle_y); + // Fall back scenario, in case we don't have a geometric bounding box at hand; + // (Due to some bugs related to bounding boxes having at least one zero dimension; For more details + // see https://bugs.launchpad.net/inkscape/+bug/318726) + g_warning("No geometric bounding box has been calculated; this is a bug that needs fixing!"); + 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) { - if (points.size() < 2) return; + if (points.size() < 2) return; - std::pair<Geom::Point, int> closest_point = std::make_pair(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED); - Geom::Coord closest_dist = NR_HUGE; + std::pair<Geom::Point, int> closest_point = std::make_pair(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_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); - if (i == points.begin() || dist < closest_dist) { - closest_point = *i; - closest_dist = dist; - } + 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); + if (i == points.begin() || dist < closest_dist) { + closest_point = *i; + closest_dist = dist; + } } - points.clear(); - points.push_back(closest_point); + points.clear(); + points.push_back(closest_point); } /* diff --git a/src/seltrans.h b/src/seltrans.h index 4f47d8e20..d0ac5dccf 100644 --- a/src/seltrans.h +++ b/src/seltrans.h @@ -53,7 +53,7 @@ public: void increaseState(); void resetState(); void setCenter(Geom::Point const &p); - void grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles); + void grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles, bool translating); void transform(Geom::Matrix const &rel_affine, Geom::Point const &norm); void ungrab(); void stamp(); @@ -118,7 +118,8 @@ private: std::vector<Geom::Point> _items_centers; std::vector<std::pair<Geom::Point, int> > _snap_points; - std::vector<std::pair<Geom::Point, int> > _bbox_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 Inkscape::SelCue _selcue; diff --git a/src/shape-editor.cpp b/src/shape-editor.cpp index dc1ec1c7e..44ad9dc9e 100644 --- a/src/shape-editor.cpp +++ b/src/shape-editor.cpp @@ -17,6 +17,7 @@ #include "sp-object.h" #include "sp-item.h" +#include "sp-lpe-item.h" #include "live_effects/lpeobject.h" #include "selection.h" #include "desktop.h" @@ -54,7 +55,8 @@ ShapeEditor::ShapeEditor(SPDesktop *dt) { this->nodepath = NULL; this->knotholder = NULL; this->hit = false; - this->listener_attached_for = NULL; + this->knotholder_listener_attached_for = NULL; + this->nodepath_listener_attached_for = NULL; } ShapeEditor::~ShapeEditor() { @@ -69,10 +71,10 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) { case SH_NODEPATH: if (this->nodepath) { old_repr = this->nodepath->repr; - if (old_repr && old_repr == listener_attached_for) { + if (old_repr && old_repr == nodepath_listener_attached_for) { sp_repr_remove_listener_by_data(old_repr, this); Inkscape::GC::release(old_repr); - listener_attached_for = NULL; + nodepath_listener_attached_for = NULL; } this->grab_node = -1; @@ -83,10 +85,10 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) { case SH_KNOTHOLDER: if (this->knotholder) { old_repr = this->knotholder->repr; - if (old_repr && old_repr == listener_attached_for) { + if (old_repr && old_repr == knotholder_listener_attached_for) { sp_repr_remove_listener_by_data(old_repr, this); Inkscape::GC::release(old_repr); - listener_attached_for = NULL; + knotholder_listener_attached_for = NULL; } if (!keep_knotholder) { @@ -256,10 +258,10 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) { // setting new listener repr = SP_OBJECT_REPR(item); - if (repr != listener_attached_for) { + if (repr != nodepath_listener_attached_for) { Inkscape::GC::anchor(repr); sp_repr_add_listener(repr, &shapeeditor_repr_events, this); - listener_attached_for = repr; + nodepath_listener_attached_for = repr; } } break; @@ -273,10 +275,10 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) { this->knotholder->update_knots(); // setting new listener repr = this->knotholder->repr; - if (repr != listener_attached_for) { + if (repr != knotholder_listener_attached_for) { Inkscape::GC::anchor(repr); sp_repr_add_listener(repr, &shapeeditor_repr_events, this); - listener_attached_for = repr; + knotholder_listener_attached_for = repr; } } break; @@ -303,10 +305,10 @@ void ShapeEditor::set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject // setting new listener Inkscape::XML::Node *repr = SP_OBJECT_REPR(lpeobject); - if (repr && repr != listener_attached_for) { + if (repr && repr != nodepath_listener_attached_for) { Inkscape::GC::anchor(repr); sp_repr_add_listener(repr, &shapeeditor_repr_events, this); - listener_attached_for = repr; + nodepath_listener_attached_for = repr; } } } @@ -317,9 +319,6 @@ void ShapeEditor::set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject Why not make a reload function in NodePath and in KnotHolder? */ void ShapeEditor::reset_item (SubType type, bool keep_knotholder) { - /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject. - SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(listener_attached_for); - switch (type) { case SH_NODEPATH: if ( (nodepath) && (IS_LIVEPATHEFFECT(nodepath->object)) ) { @@ -327,11 +326,13 @@ void ShapeEditor::reset_item (SubType type, bool keep_knotholder) set_item_lpe_path_parameter(nodepath->item, LIVEPATHEFFECT(nodepath->object), key); // the above checks for nodepath, so it is indeed a path that we are editing g_free(key); } else { + SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(nodepath_listener_attached_for); /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject. set_item(SP_ITEM(obj), SH_NODEPATH); } break; case SH_KNOTHOLDER: - if (this->knotholder) { + if ( knotholder ) { + SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(knotholder_listener_attached_for); /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject. set_item(SP_ITEM(obj), SH_KNOTHOLDER, keep_knotholder); } break; @@ -353,6 +354,9 @@ bool ShapeEditor::is_over_stroke (Geom::Point event_p, bool remember) { const SPItem *item = get_item(SH_NODEPATH); + if (!item || !SP_IS_ITEM(item)) + return false; + //Translate click point into proper coord system this->curvepoint_doc = desktop->w2d(event_p); this->curvepoint_doc *= sp_item_dt2i_affine(item); diff --git a/src/shape-editor.h b/src/shape-editor.h index ef81540ae..98dbb35d7 100644 --- a/src/shape-editor.h +++ b/src/shape-editor.h @@ -143,7 +143,8 @@ private: Geom::Point curvepoint_event; // int coords from event Geom::Point curvepoint_doc; // same, in doc coords - Inkscape::XML::Node *listener_attached_for; + Inkscape::XML::Node *knotholder_listener_attached_for; + Inkscape::XML::Node *nodepath_listener_attached_for; }; diff --git a/src/snap.cpp b/src/snap.cpp index 91d5d64ec..545607889 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -206,13 +206,6 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointTyp bool first_point, Geom::OptRect const &bbox_to_snap) const { - if (_desktop->event_context && _desktop->event_context->_snap_window_open == false) { - g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!"); - // When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context); - } - - //std::cout << "SnapManager::freeSnap -> postponed: " << snapprefs.getSnapPostponedGlobally() << std::endl; - if (!someSnapperMightSnap()) { return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } @@ -371,12 +364,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P bool first_point, Geom::OptRect const &bbox_to_snap) const { - if (_desktop->event_context && _desktop->event_context->_snap_window_open == false) { - g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!"); - // When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context); - } - - if (!someSnapperMightSnap()) { + if (!someSnapperMightSnap()) { return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } @@ -390,7 +378,10 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P items_to_ignore = _items_to_ignore; } + + // First project the mouse pointer onto the constraint Geom::Point pp = constraint.projection(p); + // Then try to snap the projected point SnappedConstraints sc; SnapperList const snappers = getSnappers(); @@ -402,7 +393,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P delete items_to_ignore; } - return findBestSnap(p, source_type, sc, true); + return findBestSnap(pp, source_type, sc, true); } /** @@ -417,13 +408,8 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P * \param p Current position of the point on the guide that is to be snapped; will be overwritten by the position of the snap target if snapping has occurred * \param guide_normal Vector normal to the guide line */ -void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal) const +void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal, SPGuideDragType drag_type) const { - if (_desktop->event_context && _desktop->event_context->_snap_window_open == false) { - g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!"); - // When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context); - } - if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) { return; } @@ -432,20 +418,27 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal) return; } + Inkscape::SnapSourceType source_type = Inkscape::SNAPSOURCE_GUIDE_ORIGIN; + if (drag_type == SP_DRAG_ROTATE) { + source_type = Inkscape::SNAPSOURCE_GUIDE; + } + // Snap to nodes SnappedConstraints sc; if (object.GuidesMightSnap()) { object.guideFreeSnap(sc, p, guide_normal); } - // Snap to guides - if (snapprefs.getSnapToGuides()) { - guide.freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, Inkscape::SNAPSOURCE_GUIDE, true, Geom::OptRect(), NULL, 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)->freeSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, source_type, true, Geom::OptRect(), NULL, NULL); + } - // We won't snap to grids, what's the use? + // 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(p, Inkscape::SNAPSOURCE_GUIDE, sc, false); s.getPoint(p); } @@ -465,12 +458,7 @@ void SnapManager::guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal) void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) const { - if (_desktop->event_context && _desktop->event_context->_snap_window_open == false) { - g_warning("The current tool tries to snap, but it hasn't yet opened the snap window. Please report this!"); - // When the context goes into dragging-mode, then Inkscape should call this: sp_event_context_snap_window_open(event_context); - } - - if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) { + if (!snapprefs.getSnapEnabledGlobally() || snapprefs.getSnapPostponedGlobally()) { return; } @@ -478,21 +466,23 @@ void SnapManager::guideConstrainedSnap(Geom::Point &p, SPGuide const &guideline) return; } + Inkscape::SnapSourceType source_type = Inkscape::SNAPSOURCE_GUIDE_ORIGIN; + // 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, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, true, Geom::OptRect(), cl, NULL); + object.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, source_type, true, Geom::OptRect(), cl, NULL); } - // Snap to guides - if (snapprefs.getSnapToGuides()) { - guide.constrainedSnap(sc, Inkscape::SnapPreferences::SNAPPOINT_GUIDE, p, Inkscape::SNAPSOURCE_GUIDE_ORIGIN, true, Geom::OptRect(), cl, NULL); - } - - // We won't snap to grids, what's the use? + // 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); + } - Inkscape::SnappedPoint const s = findBestSnap(p, Inkscape::SNAPSOURCE_GUIDE, sc, false); + Inkscape::SnappedPoint const s = findBestSnap(p, source_type, sc, false); s.getPoint(p); } @@ -922,13 +912,15 @@ Inkscape::SnappedPoint SnapManager::constrainedSnapSkew(Inkscape::SnapPreference * \param source_type Detailed description of the source type, will be used by the snap indicator * \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 * \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) const + bool constrained, + bool noCurves) const { /* @@ -950,9 +942,11 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, } // search for the closest snapped curve - Inkscape::SnappedCurve closestCurve; - if (getClosestCurve(sc.curves, closestCurve)) { - sp_list.push_back(Inkscape::SnappedPoint(closestCurve)); + if (!noCurves) { + Inkscape::SnappedCurve closestCurve; + if (getClosestCurve(sc.curves, closestCurve)) { + sp_list.push_back(Inkscape::SnappedPoint(closestCurve)); + } } if (snapprefs.getSnapIntersectionCS()) { diff --git a/src/snap.h b/src/snap.h index 866775789..e621bdb60 100644 --- a/src/snap.h +++ b/src/snap.h @@ -31,6 +31,14 @@ #include "object-snapper.h" #include "snap-preferences.h" +/* Guides */ +enum SPGuideDragType { // used both here and in desktop-events.cpp + SP_DRAG_TRANSLATE, + SP_DRAG_ROTATE, + SP_DRAG_MOVE_ORIGIN, + SP_DRAG_NONE +}; + class SPNamedView; /// Class to coordinate snapping operations @@ -103,7 +111,7 @@ public: bool first_point = true, Geom::OptRect const &bbox_to_snap = Geom::OptRect()) const; - void guideFreeSnap(Geom::Point &p, Geom::Point const &guide_normal) 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, @@ -193,7 +201,7 @@ private: 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) const; + Inkscape::SnappedPoint findBestSnap(Geom::Point const &p, Inkscape::SnapSourceType const source_type, SnappedConstraints &sc, bool constrained, bool noCurves = false) const; }; #endif /* !SEEN_SNAP_H */ diff --git a/src/snapped-point.h b/src/snapped-point.h index d071afddc..70d16b0be 100644 --- a/src/snapped-point.h +++ b/src/snapped-point.h @@ -26,6 +26,7 @@ enum SnapTargetType { SNAPTARGET_GRID_INTERSECTION, SNAPTARGET_GUIDE, SNAPTARGET_GUIDE_INTERSECTION, + SNAPTARGET_GUIDE_ORIGIN, SNAPTARGET_GRID_GUIDE_INTERSECTION, SNAPTARGET_NODE_SMOOTH, SNAPTARGET_NODE_CUSP, diff --git a/src/sp-conn-end-pair.cpp b/src/sp-conn-end-pair.cpp index 49d6fbcdb..4dc0230ff 100644 --- a/src/sp-conn-end-pair.cpp +++ b/src/sp-conn-end-pair.cpp @@ -309,6 +309,9 @@ SPConnEndPair::reroutePath(void) Geom::Point p(route.ps[i].x, route.ps[i].y); curve->lineto(p); } + + Geom::Matrix doc2item = sp_item_i2doc_affine(SP_ITEM(_path)).inverse(); + curve->transform(doc2item); } /* diff --git a/src/sp-conn-end.cpp b/src/sp-conn-end.cpp index 91ff4bc2b..0b420a98e 100644 --- a/src/sp-conn-end.cpp +++ b/src/sp-conn-end.cpp @@ -1,6 +1,7 @@ #include <cstring> #include <string> +#include <limits> #include "display/curve.h" #include "libnr/nr-matrix-fns.h" @@ -9,11 +10,13 @@ #include "sp-path.h" #include "uri.h" #include "document.h" +#include "sp-item-group.h" +#include "2geom/path.h" +#include "2geom/pathvector.h" +#include "2geom/path-intersection.h" static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]); -static Geom::Point calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p); -static double signed_one(double const x); SPConnEnd::SPConnEnd(SPObject *const owner) : ref(owner), @@ -35,6 +38,95 @@ get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[ return anc_sofar; } + +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(); + + // if this is a group... + if (SP_IS_GROUP(&item)) { + SPGroup* group = SP_GROUP(&item); + + // consider all first-order children + double child_pos = initial_pos; + unsigned child_index; + 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; + } + } + return *intersect_pos != initial_pos; + } + + // if this is a shape... + if (!SP_IS_SHAPE(&item)) return false; + + // make sure it has an associated curve + 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++) { + const Geom::Crossings& cr = *i; + + 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; + } + } + } + + item_curve->unref(); + + return *intersect_pos != initial_pos; +} + + +// This function returns the outermost intersection point between the path (a connector) +// 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(); + + // Copy the curve and apply transformations up to common ancestor. + SPCurve* conn_curve = conn.curve->copy(); + conn_curve->transform(conn_transform); + + // Find the intersection. + bool result = try_get_intersect_point_with_item_recursive(conn_curve, item, item_transform, + at_start, intersect_pos, intersect_index); + + // Free the curve copy. + conn_curve->unref(); + + return result; +} + + static void sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPPath *const path, @@ -50,98 +142,28 @@ sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPItem *h2attItem[2]; path->connEndPair.getAttachedItems(h2attItem); - if ( !h2attItem[0] && !h2attItem[1] ) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; - } SPItem const *const path_item = SP_ITEM(path); SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor)); - if (h2attItem[0] != NULL && h2attItem[1] != NULL) { - /* Initial end-points: centre of attached object. */ - Geom::Point h2endPt_icoordsys[2]; - Geom::Matrix h2i2anc[2]; - Geom::Rect h2bbox_icoordsys[2]; - Geom::Point last_seg_endPt[2] = { - *(path->curve->second_point()), - *(path->curve->penultimate_point()) - }; - for (unsigned h = 0; h < 2; ++h) { - Geom::OptRect bbox = h2attItem[h]->getBounds(Geom::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; - } - h2bbox_icoordsys[h] = *bbox; - h2i2anc[h] = i2anc_affine(h2attItem[h], ancestor); - h2endPt_icoordsys[h] = h2bbox_icoordsys[h].midpoint(); - } - - // For each attached object, change the corresponding point to be - // on the edge of the bbox. - Geom::Point h2endPt_pcoordsys[2]; - for (unsigned h = 0; h < 2; ++h) { - h2endPt_icoordsys[h] = calc_bbox_conn_pt(h2bbox_icoordsys[h], - ( last_seg_endPt[h] * h2i2anc[h].inverse() )); - h2endPt_pcoordsys[h] = h2endPt_icoordsys[h] * h2i2anc[h] * path2anc.inverse(); - } - change_endpts(path->curve, h2endPt_pcoordsys); - } else { - // We leave the unattached endpoint where it is, and adjust the - // position of the attached endpoint to be on the edge of the bbox. - unsigned ind; - Geom::Point other_endpt; - Geom::Point last_seg_pt; - if (h2attItem[0] != NULL) { - other_endpt = *(path->curve->last_point()); - last_seg_pt = *(path->curve->second_point()); - ind = 0; - } - else { - other_endpt = *(path->curve->first_point()); - last_seg_pt = *(path->curve->penultimate_point()); - ind = 1; - } - Geom::Point h2endPt_icoordsys[2]; - Geom::Matrix h2i2anc; - - Geom::Rect otherpt_rect = Geom::Rect(other_endpt, other_endpt); - Geom::Rect h2bbox_icoordsys[2] = { otherpt_rect, otherpt_rect }; - Geom::OptRect bbox = h2attItem[ind]->getBounds(Geom::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); + Geom::Point endPts[2] = { *(path->curve->first_point()), *(path->curve->last_point()) }; + + 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; + 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); } - return; } - - h2bbox_icoordsys[ind] = *bbox; - h2i2anc = i2anc_affine(h2attItem[ind], ancestor); - h2endPt_icoordsys[ind] = h2bbox_icoordsys[ind].midpoint(); - - h2endPt_icoordsys[!ind] = other_endpt; - - // For the attached object, change the corresponding point to be - // on the edge of the bbox. - Geom::Point h2endPt_pcoordsys[2]; - h2endPt_icoordsys[ind] = calc_bbox_conn_pt(h2bbox_icoordsys[ind], - ( last_seg_pt * h2i2anc.inverse() )); - h2endPt_pcoordsys[ind] = h2endPt_icoordsys[ind] * h2i2anc * path2anc.inverse(); - - // Leave the other where it is. - h2endPt_pcoordsys[!ind] = other_endpt; - - change_endpts(path->curve, h2endPt_pcoordsys); } + change_endpts(path->curve, endPts); if (updatePathRepr) { path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); path->updateRepr(); @@ -180,45 +202,6 @@ sp_conn_adjust_path(SPPath *const path) sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr); } -static Geom::Point -calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p) -{ - using Geom::X; - using Geom::Y; - Geom::Point const ctr(bbox.midpoint()); - Geom::Point const lengths(bbox.dimensions()); - if ( ctr == p ) { - /* Arbitrarily choose centre of right edge. */ - return Geom::Point(ctr[X] + .5 * lengths[X], - ctr[Y]); - } - Geom::Point const cp( p - ctr ); - Geom::Dim2 const edgeDim = ( ( fabs(lengths[Y] * cp[X]) < - fabs(lengths[X] * cp[Y]) ) - ? Y - : X ); - Geom::Dim2 const otherDim = (Geom::Dim2) !edgeDim; - Geom::Point offset; - offset[edgeDim] = (signed_one(cp[edgeDim]) - * lengths[edgeDim]); - offset[otherDim] = (lengths[edgeDim] - * cp[otherDim] - / fabs(cp[edgeDim])); - g_assert((offset[otherDim] >= 0) == (cp[otherDim] >= 0)); -#ifndef NDEBUG - for (unsigned d = 0; d < 2; ++d) { - g_assert(fabs(offset[d]) <= lengths[d] + .125); - } -#endif - return ctr + .5 * offset; -} - -static double signed_one(double const x) -{ - return (x < 0 - ? -1. - : 1.); -} static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]) diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp index ff2e39044..12ba0ed0e 100644 --- a/src/sp-ellipse.cpp +++ b/src/sp-ellipse.cpp @@ -28,6 +28,7 @@ #include "display/curve.h" #include <glibmm/i18n.h> #include <2geom/transforms.h> +#include <2geom/pathvector.h> #include "document.h" #include "sp-ellipse.h" @@ -184,6 +185,18 @@ sp_genericellipse_update_patheffect(SPLPEItem *lpeitem, bool write) /* Can't we use arcto in this method? */ static void sp_genericellipse_set_shape(SPShape *shape) { + if (sp_lpe_item_has_broken_path_effect(SP_LPE_ITEM(shape))) { + g_warning ("The ellipse shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as ellipse will remove the bad LPE"); + if (SP_OBJECT_REPR(shape)->attribute("d")) { + // unconditionally read the curve from d, if any, to preserve appearance + Geom::PathVector pv = sp_svg_read_pathv(SP_OBJECT_REPR(shape)->attribute("d")); + SPCurve *cold = new SPCurve(pv); + sp_shape_set_curve_insync (shape, cold, TRUE); + cold->unref(); + } + return; + } + double rx, ry, s, e; double x0, y0, x1, y1, x2, y2, x3, y3; double len; @@ -880,10 +893,11 @@ sp_arc_position_set(SPArc *arc, gdouble x, gdouble y, gdouble rx, gdouble ry) ge->rx.computed = rx; ge->ry.computed = ry; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + // those pref values are in degrees, while we want radians if (prefs->getDouble("/tools/shapes/arc/start", 0.0) != 0) - ge->start = prefs->getDouble("/tools/shapes/arc/start", 0.0); + ge->start = prefs->getDouble("/tools/shapes/arc/start", 0.0) * M_PI / 180; if (prefs->getDouble("/tools/shapes/arc/end", 0.0) != 0) - ge->end = prefs->getDouble("/tools/shapes/arc/end", 0.0); + ge->end = prefs->getDouble("/tools/shapes/arc/end", 0.0) * M_PI / 180; if (!prefs->getBool("/tools/shapes/arc/open")) ge->closed = 1; else diff --git a/src/sp-guide.cpp b/src/sp-guide.cpp index e6983a681..f5edf7d97 100644 --- a/src/sp-guide.cpp +++ b/src/sp-guide.cpp @@ -404,35 +404,45 @@ void sp_guide_set_normal(SPGuide const &guide, Geom::Point const normal_to_line, /** * Returns a human-readable description of the guideline for use in dialog boxes and status bar. + * If verbose is false, only positioning information is included (useful for dialogs). * * The caller is responsible for freeing the string. */ -char *sp_guide_description(SPGuide const *guide) +char *sp_guide_description(SPGuide const *guide, const bool verbose) { using Geom::X; using Geom::Y; - GString *position_string_x = SP_PX_TO_METRIC_STRING(guide->point_on_line[X], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric()); - GString *position_string_y = SP_PX_TO_METRIC_STRING(guide->point_on_line[Y], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric()); + GString *position_string_x = SP_PX_TO_METRIC_STRING(guide->point_on_line[X], + SP_ACTIVE_DESKTOP->namedview->getDefaultMetric()); + GString *position_string_y = SP_PX_TO_METRIC_STRING(guide->point_on_line[Y], + SP_ACTIVE_DESKTOP->namedview->getDefaultMetric()); - const gchar *shortcuts = _("<b>drag</b> to move, <b>Shift+drag</b> to rotate, <b>Ctrl</b>+click to delete"); + gchar *shortcuts = g_strdup_printf("; %s", _("<b>Shift+drag</b> to rotate, <b>Ctrl+drag</b> to move origin, <b>Del</b> to delete")); + gchar *descr; if ( are_near(guide->normal_to_line, component_vectors[X]) || are_near(guide->normal_to_line, -component_vectors[X]) ) { - return g_strdup_printf(_("vertical, at %s; %s"), position_string_x->str, shortcuts); + descr = g_strdup_printf(_("vertical, at %s"), position_string_x->str); } else if ( are_near(guide->normal_to_line, component_vectors[Y]) || are_near(guide->normal_to_line, -component_vectors[Y]) ) { - return g_strdup_printf(_("horizontal, at %s; %s"), position_string_y->str, shortcuts); + descr = g_strdup_printf(_("horizontal, at %s"), position_string_y->str); } else { double const radians = guide->angle(); double const degrees = Geom::rad_to_deg(radians); int const degrees_int = (int) round(degrees); - return g_strdup_printf(_("at %d degrees, through (%s,%s); %s"), - degrees_int, position_string_x->str, position_string_y->str, shortcuts); + descr = g_strdup_printf(_("at %d degrees, through (%s,%s)"), + degrees_int, position_string_x->str, position_string_y->str); } g_string_free(position_string_x, TRUE); g_string_free(position_string_y, TRUE); + + if (verbose) { + descr = g_strconcat(descr, shortcuts, NULL); + } + g_free(shortcuts); + return descr; } void sp_guide_remove(SPGuide *guide) diff --git a/src/sp-guide.h b/src/sp-guide.h index 593e4583f..6bf541cd1 100644 --- a/src/sp-guide.h +++ b/src/sp-guide.h @@ -59,7 +59,7 @@ void sp_guide_moveto(SPGuide const &guide, Geom::Point const point_on_line, bool void sp_guide_set_normal(SPGuide const &guide, Geom::Point const normal_to_line, bool const commit); void sp_guide_remove(SPGuide *guide); -char *sp_guide_description(SPGuide const *guide); +char *sp_guide_description(SPGuide const *guide, const bool verbose = true); #endif /* !SP_GUIDE_H */ diff --git a/src/sp-item-group.cpp b/src/sp-item-group.cpp index 9c19ce75a..7ac7880a7 100644 --- a/src/sp-item-group.cpp +++ b/src/sp-item-group.cpp @@ -43,6 +43,7 @@ #include "inkscape.h" #include "desktop-handles.h" #include "selection.h" +#include "live_effects/effect.h" #include "live_effects/lpeobject.h" #include "live_effects/lpeobject-reference.h" #include "sp-title.h" @@ -72,7 +73,7 @@ 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_update_patheffect(SPLPEItem *lpeitem, bool write); -static void sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup); +static void sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup, bool write); static SPLPEItemClass * parent_class; @@ -856,30 +857,39 @@ sp_group_update_patheffect (SPLPEItem *lpeitem, bool write) } } - sp_group_perform_patheffect(SP_GROUP(lpeitem), SP_GROUP(lpeitem)); + sp_group_perform_patheffect(SP_GROUP(lpeitem), SP_GROUP(lpeitem), write); } } static void -sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup) +sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup, bool write) { GSList const *item_list = sp_item_group_item_list(SP_GROUP(group)); for ( GSList const *iter = item_list; iter; iter = iter->next ) { SPObject *subitem = static_cast<SPObject *>(iter->data); if (SP_IS_GROUP(subitem)) { - sp_group_perform_patheffect(SP_GROUP(subitem), topgroup); + sp_group_perform_patheffect(SP_GROUP(subitem), topgroup, write); } else if (SP_IS_SHAPE(subitem)) { - SPCurve * c = sp_shape_get_curve(SP_SHAPE(subitem)); + SPCurve * c = NULL; + if (SP_IS_PATH(subitem)) { + c = sp_path_get_original_curve(SP_PATH(subitem)); + } else { + c = sp_shape_get_curve(SP_SHAPE(subitem)); + } // only run LPEs when the shape has a curve defined if (c) { sp_lpe_item_perform_path_effect(SP_LPE_ITEM(topgroup), c); sp_shape_set_curve(SP_SHAPE(subitem), c, TRUE); - Inkscape::XML::Node *repr = SP_OBJECT_REPR(subitem); - - gchar *str = sp_svg_write_path(c->get_pathvector()); - repr->setAttribute("d", str); - g_free(str); + if (write) { + Inkscape::XML::Node *repr = SP_OBJECT_REPR(subitem); + gchar *str = sp_svg_write_path(c->get_pathvector()); + repr->setAttribute("d", str); +#ifdef GROUP_VERBOSE +g_message("sp_group_perform_patheffect writes 'd' attribute"); +#endif + g_free(str); + } c->unref(); } diff --git a/src/sp-item.cpp b/src/sp-item.cpp index 662dc1cac..395048280 100644 --- a/src/sp-item.cpp +++ b/src/sp-item.cpp @@ -1062,7 +1062,7 @@ sp_item_description(SPItem *item) const gchar *label = SP_OBJECT_STYLE(item)->filter.href->getObject()->label(); gchar *snew; if (label) { - snew = g_strdup_printf (_("%s; <i>filtered (%s)</i>"), s, label); + snew = g_strdup_printf (_("%s; <i>filtered (%s)</i>"), s, _(label)); } else { snew = g_strdup_printf (_("%s; <i>filtered</i>"), s); } diff --git a/src/sp-lpe-item.cpp b/src/sp-lpe-item.cpp index 90e9b2d6d..6b71541e6 100644 --- a/src/sp-lpe-item.cpp +++ b/src/sp-lpe-item.cpp @@ -56,7 +56,6 @@ static void sp_lpe_item_remove_child (SPObject * object, Inkscape::XML::Node * c static void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable); -static void lpeobject_ref_changed(SPObject *old_ref, SPObject *ref, SPLPEItem *lpeitem); static void lpeobject_ref_modified(SPObject *href, guint flags, SPLPEItem *lpeitem); static void sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem); @@ -120,7 +119,7 @@ sp_lpe_item_init(SPLPEItem *lpeitem) lpeitem->path_effect_list = new PathEffectList(); lpeitem->current_path_effect = NULL; - new (&lpeitem->lpe_modified_connection) sigc::connection(); + lpeitem->lpe_modified_connection_list = new std::list<sigc::connection>(); } static void @@ -154,8 +153,14 @@ sp_lpe_item_release(SPObject *object) { SPLPEItem *lpeitem = (SPLPEItem *) object; - lpeitem->lpe_modified_connection.disconnect(); - lpeitem->lpe_modified_connection.~connection(); + // disconnect all modified listeners: + for (std::list<sigc::connection>::iterator mod_it = lpeitem->lpe_modified_connection_list->begin(); + mod_it != lpeitem->lpe_modified_connection_list->end(); ++mod_it) + { + mod_it->disconnect(); + } + delete lpeitem->lpe_modified_connection_list; + lpeitem->lpe_modified_connection_list = NULL; PathEffectList::iterator it = lpeitem->path_effect_list->begin(); while ( it != lpeitem->path_effect_list->end() ) { @@ -165,7 +170,8 @@ sp_lpe_item_release(SPObject *object) it = lpeitem->path_effect_list->erase(it); } // delete the list itself - delete SP_LPE_ITEM(object)->path_effect_list; + delete lpeitem->path_effect_list; + lpeitem->path_effect_list = NULL; if (((SPObjectClass *) parent_class)->release) ((SPObjectClass *) parent_class)->release(object); @@ -187,6 +193,14 @@ sp_lpe_item_set(SPObject *object, unsigned int key, gchar const *value) // Disable the path effects while populating the LPE list sp_lpe_item_enable_path_effects(lpeitem, false); + // disconnect all modified listeners: + for ( std::list<sigc::connection>::iterator mod_it = lpeitem->lpe_modified_connection_list->begin(); + mod_it != lpeitem->lpe_modified_connection_list->end(); + ++mod_it) + { + mod_it->disconnect(); + } + lpeitem->lpe_modified_connection_list->clear(); // Clear the path effect list PathEffectList::iterator it = lpeitem->path_effect_list->begin(); while ( it != lpeitem->path_effect_list->end() ) @@ -202,10 +216,7 @@ sp_lpe_item_set(SPObject *object, unsigned int key, gchar const *value) std::string href; while (std::getline(iss, href, ';')) { - Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(SP_OBJECT(lpeitem)); - path_effect_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(lpeobject_ref_changed), SP_LPE_ITEM(object))); - // Now do the attaching, which emits the changed signal. - // Fixme, it should not do this changed signal and updating before all effects are added to the path_effect_list + Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(object); try { path_effect_ref->link(href.c_str()); } catch (Inkscape::BadURIException e) { @@ -216,7 +227,11 @@ sp_lpe_item_set(SPObject *object, unsigned int key, gchar const *value) } lpeitem->path_effect_list->push_back(path_effect_ref); - if ( !(path_effect_ref->lpeobject && path_effect_ref->lpeobject->get_lpe()) ) { + if ( path_effect_ref->lpeobject && path_effect_ref->lpeobject->get_lpe() ) { + // connect modified-listener + lpeitem->lpe_modified_connection_list->push_back( + path_effect_ref->lpeobject->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), lpeitem)) ); + } else { // something has gone wrong in finding the right patheffect. g_warning("Unknown LPE type specified, LPE stack effectively disabled"); // keep the effect in the lpestack, so the whole stack is effectively disabled but maintained @@ -283,11 +298,13 @@ sp_lpe_item_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape:: { SPLPEItem *lpeitem = (SPLPEItem *) object; - if ( sp_lpe_item_has_path_effect(lpeitem) ) { - std::string href = patheffectlist_write_svg(*lpeitem->path_effect_list); - repr->setAttribute("inkscape:path-effect", href.c_str()); - } else { - repr->setAttribute("inkscape:path-effect", NULL); + if (flags & SP_OBJECT_WRITE_EXT) { + if ( sp_lpe_item_has_path_effect(lpeitem) ) { + std::string href = patheffectlist_write_svg(*lpeitem->path_effect_list); + repr->setAttribute("inkscape:path-effect", href.c_str()); + } else { + repr->setAttribute("inkscape:path-effect", NULL); + } } if (((SPObjectClass *)(parent_class))->write) { @@ -405,28 +422,14 @@ sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write) } /** - * Gets called when (re)attached to another lpeobject. - */ -static void -lpeobject_ref_changed(SPObject *old_ref, SPObject *ref, SPLPEItem *lpeitem) -{ - if (old_ref) { - sp_signal_disconnect_by_data(old_ref, lpeitem); - } - if ( IS_LIVEPATHEFFECT(ref) && ref != lpeitem ) - { - lpeitem->lpe_modified_connection.disconnect(); - lpeitem->lpe_modified_connection = ref->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), lpeitem)); - lpeobject_ref_modified(ref, 0, lpeitem); - } -} - -/** - * Gets called when lpeobject repr contents change: i.e. parameter change. + * Gets called when any of the lpestack's lpeobject repr contents change: i.e. parameter change in any of the stacked LPEs */ static void lpeobject_ref_modified(SPObject */*href*/, guint /*flags*/, SPLPEItem *lpeitem) { +#ifdef SHAPE_VERBOSE + g_message("lpeobject_ref_modified"); +#endif sp_lpe_item_update_patheffect (lpeitem, true, true); } @@ -541,7 +544,11 @@ void sp_lpe_item_remove_current_path_effect(SPLPEItem *lpeitem, bool keep_paths) new_list.remove(lperef); //current lpe ref is always our 'own' pointer from the path_effect_list std::string r = patheffectlist_write_svg(new_list); - SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", r.c_str()); + if (!r.empty()) { + SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", r.c_str()); + } else { + SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", NULL); + } if (!keep_paths) { sp_lpe_item_cleanup_original_path_recursive(lpeitem); @@ -598,6 +605,24 @@ void sp_lpe_item_up_current_path_effect(SPLPEItem *lpeitem) sp_lpe_item_cleanup_original_path_recursive(lpeitem); } +/** used for shapes so they can see if they should also disable shape calculation and read from d= */ +bool sp_lpe_item_has_broken_path_effect(SPLPEItem *lpeitem) +{ + if (lpeitem->path_effect_list->empty()) + return false; + + // go through the list; if some are unknown or invalid, return true + PathEffectList effect_list = sp_lpe_item_get_effect_list(lpeitem); + for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); it++) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (!lpeobj || !lpeobj->get_lpe()) + return true; + } + + return false; +} + bool sp_lpe_item_has_path_effect(SPLPEItem *lpeitem) { @@ -729,8 +754,9 @@ PathEffectList sp_lpe_item_get_effect_list(SPLPEItem *lpeitem) Inkscape::LivePathEffect::LPEObjectReference* sp_lpe_item_get_current_lpereference(SPLPEItem *lpeitem) { - if (!lpeitem->current_path_effect && !lpeitem->path_effect_list->empty()) + if (!lpeitem->current_path_effect && !lpeitem->path_effect_list->empty()) { sp_lpe_item_set_current_path_effect(lpeitem, lpeitem->path_effect_list->back()); + } return lpeitem->current_path_effect; } @@ -749,7 +775,6 @@ bool sp_lpe_item_set_current_path_effect(SPLPEItem *lpeitem, Inkscape::LivePathE { for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); it++) { if ((*it)->lpeobject_repr == lperef->lpeobject_repr) { - lpeobject_ref_changed(NULL, (*it)->lpeobject, SP_LPE_ITEM(lpeitem)); // FIXME: explain why this is here? lpeitem->current_path_effect = (*it); // current_path_effect should always be a pointer from the path_effect_list ! return true; } diff --git a/src/sp-lpe-item.h b/src/sp-lpe-item.h index e9561c2c2..4823390de 100644 --- a/src/sp-lpe-item.h +++ b/src/sp-lpe-item.h @@ -39,10 +39,10 @@ struct SPLPEItem : public SPItem { int path_effects_enabled; PathEffectList* path_effect_list; + std::list<sigc::connection> *lpe_modified_connection_list; // this list contains the connections for listening to lpeobject parameter changes + Inkscape::LivePathEffect::LPEObjectReference* current_path_effect; std::vector<Inkscape::Display::TemporaryItem*> lpe_helperpaths; - - sigc::connection lpe_modified_connection; }; struct SPLPEItemClass { @@ -64,6 +64,7 @@ void sp_lpe_item_remove_current_path_effect(SPLPEItem *lpeitem, bool keep_paths) void sp_lpe_item_down_current_path_effect(SPLPEItem *lpeitem); void sp_lpe_item_up_current_path_effect(SPLPEItem *lpeitem); bool sp_lpe_item_has_path_effect(SPLPEItem *lpeitem); +bool sp_lpe_item_has_broken_path_effect(SPLPEItem *lpeitem); bool sp_lpe_item_has_path_effect_recursive(SPLPEItem *lpeitem); Inkscape::LivePathEffect::Effect* sp_lpe_item_has_path_effect_of_type(SPLPEItem *lpeitem, int type); bool sp_lpe_item_can_accept_freehand_shape(SPLPEItem *lpeitem); diff --git a/src/sp-path.cpp b/src/sp-path.cpp index 04ad386d9..2120ddd64 100644 --- a/src/sp-path.cpp +++ b/src/sp-path.cpp @@ -320,6 +320,9 @@ sp_path_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML: repr = xml_doc->createElement("svg:path"); } +#ifdef PATH_VERBOSE +g_message("sp_path_write writes 'd' attribute"); +#endif if ( shape->curve != NULL ) { gchar *str = sp_svg_write_path(shape->curve->get_pathvector()); repr->setAttribute("d", str); @@ -328,13 +331,15 @@ sp_path_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML: repr->setAttribute("d", NULL); } - SPPath *path = (SPPath *) object; - if ( path->original_curve != NULL ) { - gchar *str = sp_svg_write_path(path->original_curve->get_pathvector()); - repr->setAttribute("inkscape:original-d", str); - g_free(str); - } else { - repr->setAttribute("inkscape:original-d", NULL); + if (flags & SP_OBJECT_WRITE_EXT) { + SPPath *path = (SPPath *) object; + if ( path->original_curve != NULL ) { + gchar *str = sp_svg_write_path(path->original_curve->get_pathvector()); + repr->setAttribute("inkscape:original-d", str); + g_free(str); + } else { + repr->setAttribute("inkscape:original-d", NULL); + } } SP_PATH(shape)->connEndPair.writeRepr(repr); @@ -377,7 +382,7 @@ sp_path_set_transform(SPItem *item, Geom::Matrix const &xform) // Transform the original-d path if this is a valid LPE item, other else the (ordinary) path if (path->original_curve && SP_IS_LPE_ITEM(item) && - sp_lpe_item_has_path_effect(SP_LPE_ITEM(item))) { + sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(item))) { path->original_curve->transform(xform); } else { shape->curve->transform(xform); @@ -409,6 +414,10 @@ sp_path_update_patheffect(SPLPEItem *lpeitem, bool write) SPPath * const path = (SPPath *) lpeitem; Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape); +#ifdef PATH_VERBOSE +g_message("sp_path_update_patheffect"); +#endif + if (path->original_curve && sp_lpe_item_has_path_effect_recursive(lpeitem)) { SPCurve *curve = path->original_curve->copy(); /* if a path does not have an lpeitem applied, then reset the curve to the original_curve. @@ -418,6 +427,9 @@ sp_path_update_patheffect(SPLPEItem *lpeitem, bool write) bool success = sp_lpe_item_perform_path_effect(SP_LPE_ITEM(shape), curve); if (success && write) { // could also do SP_OBJECT(shape)->updateRepr(); but only the d attribute needs updating. +#ifdef PATH_VERBOSE +g_message("sp_path_update_patheffect writes 'd' attribute"); +#endif if ( shape->curve != NULL ) { gchar *str = sp_svg_write_path(shape->curve->get_pathvector()); repr->setAttribute("d", str); @@ -425,7 +437,7 @@ sp_path_update_patheffect(SPLPEItem *lpeitem, bool write) } else { repr->setAttribute("d", NULL); } - } else { + } else if (!success) { // LPE was unsuccesfull. Read the old 'd'-attribute. if (gchar const * value = repr->attribute("d")) { Geom::PathVector pv = sp_svg_read_pathv(value); @@ -487,7 +499,7 @@ SPCurve* sp_path_get_curve_for_edit (SPPath *path) { if (path->original_curve && SP_IS_LPE_ITEM(path) && - sp_lpe_item_has_path_effect(SP_LPE_ITEM(path))) { + sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(path))) { return sp_path_get_original_curve(path); } else { return sp_shape_get_curve( (SPShape *) path ); @@ -502,7 +514,7 @@ const SPCurve* sp_path_get_curve_reference (SPPath *path) { if (path->original_curve && SP_IS_LPE_ITEM(path) && - sp_lpe_item_has_path_effect(SP_LPE_ITEM(path))) { + sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(path))) { return path->original_curve; } else { return path->curve; diff --git a/src/sp-pattern.cpp b/src/sp-pattern.cpp index 90af65b97..ec0d0d576 100644 --- a/src/sp-pattern.cpp +++ b/src/sp-pattern.cpp @@ -432,6 +432,7 @@ pattern_ref_modified (SPObject */*ref*/, guint /*flags*/, SPPattern *pattern) { if (SP_IS_OBJECT (pattern)) SP_OBJECT (pattern)->requestModified(SP_OBJECT_MODIFIED_FLAG); + /* Conditional to avoid causing infinite loop if there's a cycle in the href chain. */ } guint diff --git a/src/sp-shape.cpp b/src/sp-shape.cpp index e7ded6303..519002e9e 100644 --- a/src/sp-shape.cpp +++ b/src/sp-shape.cpp @@ -409,9 +409,13 @@ sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai) int counter[4] = {0}; Geom::PathVector const & pathv = shape->curve->get_pathvector(); - for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - // START position - Geom::Matrix const m (sp_shape_marker_get_transform_at_start(path_it->front())); + + // the first vertex should get a start marker, the last an end marker, and all the others a mid marker + // see bug 456148 + + // START marker + { + Geom::Matrix const m (sp_shape_marker_get_transform_at_start(pathv.begin()->front())); for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START if ( shape->marker[i] ) { sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, @@ -420,18 +424,53 @@ sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai) counter[i]++; } } + } - // MID position - if ( (shape->marker[SP_MARKER_LOC_MID] || shape->marker[SP_MARKER_LOC]) && (path_it->size_default() > 1) ) { - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve - while (curve_it2 != path_it->end_default()) + // MID marker + if (shape->marker[SP_MARKER_LOC_MID] || shape->marker[SP_MARKER_LOC]) { + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, don't draw mid marker there { - /* Put marker between curve_it1 and curve_it2. - * Loop to end_default (so including closing segment), because when a path is closed, - * there should be a midpoint marker between last segment and closing straight line segment - */ - Geom::Matrix const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2)); + Geom::Matrix const m (sp_shape_marker_get_transform_at_start(path_it->front())); + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + if ( shape->marker[i] ) { + sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, + NR_ARENA_ITEM_GET_KEY(ai) + i, counter[i], m, + style->stroke_width.computed); + counter[i]++; + } + } + } + // MID position + if ( path_it->size_default() > 1) { + Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve + Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve + while (curve_it2 != path_it->end_default()) + { + /* Put marker between curve_it1 and curve_it2. + * Loop to end_default (so including closing segment), because when a path is closed, + * there should be a midpoint marker between last segment and closing straight line segment + */ + Geom::Matrix const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2)); + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + if (shape->marker[i]) { + sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, + NR_ARENA_ITEM_GET_KEY(ai) + i, counter[i], m, + style->stroke_width.computed); + counter[i]++; + } + } + + ++curve_it1; + ++curve_it2; + } + } + // END position + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); + Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve); for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if (shape->marker[i]) { sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, @@ -440,30 +479,28 @@ sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai) counter[i]++; } } - - ++curve_it1; - ++curve_it2; } } + } - // END position - if ( shape->marker[SP_MARKER_LOC_END] || shape->marker[SP_MARKER_LOC] ) { - /* Get reference to last curve in the path. - * For moveto-only path, this returns the "closing line segment". */ - unsigned int index = path_it->size_default(); - if (index > 0) { - index--; - } - Geom::Curve const &lastcurve = (*path_it)[index]; - Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve); + // END marker + if ( shape->marker[SP_MARKER_LOC_END] || shape->marker[SP_MARKER_LOC] ) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; + Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve); - for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END - if (shape->marker[i]) { - sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, - NR_ARENA_ITEM_GET_KEY(ai) + i, counter[i], m, - style->stroke_width.computed); - counter[i]++; - } + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if (shape->marker[i]) { + sp_marker_show_instance ((SPMarker* ) shape->marker[i], ai, + NR_ARENA_ITEM_GET_KEY(ai) + i, counter[i], m, + style->stroke_width.computed); + counter[i]++; } } } @@ -522,38 +559,62 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const & // Union with bboxes of the markers, if any if (sp_shape_has_markers (shape)) { - /* TODO: make code prettier: lots of variables can be taken out of the loop! */ + /** \todo make code prettier! */ Geom::PathVector const & pathv = shape->curve->get_pathvector(); - for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - for (unsigned i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START - if ( shape->marker[i] ) { - SPMarker* marker = SP_MARKER (shape->marker[i]); - SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); - - if (marker_item) { - Geom::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front())); - if (!marker->orient_auto) { - Geom::Point transl = tr.translation(); - tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); - } - if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { - tr = Geom::Scale(style->stroke_width.computed) * tr; - } - - // total marker transform - tr = marker_item->transform * marker->c2p * tr * transform; - - // get bbox of the marker with that transform - NRRect marker_bbox; - sp_item_invoke_bbox (marker_item, &marker_bbox, from_2geom(tr), true); - // union it with the shape bbox - nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + // START marker + for (unsigned i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( shape->marker[i] ) { + SPMarker* marker = SP_MARKER (shape->marker[i]); + SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); + + if (marker_item) { + Geom::Matrix tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front())); + if (!marker->orient_auto) { + Geom::Point transl = tr.translation(); + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } + + // total marker transform + tr = marker_item->transform * marker->c2p * tr * transform; + + // get bbox of the marker with that transform + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, from_2geom(tr), true); + // union it with the shape bbox + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); } } - - for (unsigned i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID - if ( shape->marker[i] && (path_it->size_default() > 1) ) { + } + // MID marker + for (unsigned i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + SPMarker* marker = SP_MARKER (shape->marker[i]); + if ( !shape->marker[i] ) continue; + SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); + if ( !marker_item ) continue; + + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there + { + Geom::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front())); + if (!marker->orient_auto) { + Geom::Point transl = tr.translation(); + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); + } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } + tr = marker_item->transform * marker->c2p * tr * transform; + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, from_2geom(tr), true); + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + } + // MID position + if ( path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) @@ -566,63 +627,75 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const & SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); if (marker_item) { - Geom::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); - if (!marker->orient_auto) { - Geom::Point transl = tr.translation(); - tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); - } - if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { - tr = Geom::Scale(style->stroke_width.computed) * tr; - } - - // total marker transform - tr = marker_item->transform * marker->c2p * tr * transform; - - // get bbox of the marker with that transform - NRRect marker_bbox; - sp_item_invoke_bbox (marker_item, &marker_bbox, from_2geom(tr), true); - // union it with the shape bbox - nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + Geom::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); + if (!marker->orient_auto) { + Geom::Point transl = tr.translation(); + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); + } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } + tr = marker_item->transform * marker->c2p * tr * transform; + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, from_2geom(tr), true); + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); } ++curve_it1; ++curve_it2; } } + // END position + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); + Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); + if (!marker->orient_auto) { + Geom::Point transl = tr.translation(); + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); + } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } + tr = marker_item->transform * marker->c2p * tr * transform; + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, tr, true); + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + } } + } + // END marker + for (unsigned i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( shape->marker[i] ) { + SPMarker* marker = SP_MARKER (shape->marker[i]); + SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); + + if (marker_item) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; - for (unsigned i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END - if ( shape->marker[i] ) { - SPMarker* marker = SP_MARKER (shape->marker[i]); - SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (marker)); - - if (marker_item) { - /* Get reference to last curve in the path. - * For moveto-only path, this returns the "closing line segment". */ - unsigned int index = path_it->size_default(); - if (index > 0) { - index--; - } - Geom::Curve const &lastcurve = (*path_it)[index]; - - Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); - if (!marker->orient_auto) { - Geom::Point transl = tr.translation(); - tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); - } - if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { - tr = Geom::Scale(style->stroke_width.computed) * tr; - } - - // total marker transform - tr = marker_item->transform * marker->c2p * tr * transform; - - // get bbox of the marker with that transform - NRRect marker_bbox; - sp_item_invoke_bbox (marker_item, &marker_bbox, tr, true); - // union it with the shape bbox - nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); + Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); + if (!marker->orient_auto) { + Geom::Point transl = tr.translation(); + tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } + + // total marker transform + tr = marker_item->transform * marker->c2p * tr * transform; + + // get bbox of the marker with that transform + NRRect marker_bbox; + sp_item_invoke_bbox (marker_item, &marker_bbox, tr, true); + // union it with the shape bbox + nr_rect_d_union (&cbbox, &cbbox, &marker_bbox); } } } @@ -693,57 +766,67 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx) sp_print_stroke (ctx, shape->curve->get_pathvector(), &i2d, style, &pbox, &dbox, &bbox); } - /* TODO: make code prettier: lots of variables can be taken out of the loop! */ + /** \todo make code prettier */ Geom::PathVector const & pathv = shape->curve->get_pathvector(); - for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - if ( shape->marker[SP_MARKER_LOC_START] || shape->marker[SP_MARKER_LOC]) { - Geom::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front())); - if (shape->marker[SP_MARKER_LOC_START]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC_START], tr, style, ctx); - } - if (shape->marker[SP_MARKER_LOC]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC], tr, style, ctx); - } + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( shape->marker[i] ) { + Geom::Matrix tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front())); + sp_shape_print_invoke_marker_printing(shape->marker[i], tr, style, ctx); } - - if ( (shape->marker[SP_MARKER_LOC_MID] || shape->marker[SP_MARKER_LOC]) && (path_it->size_default() > 1) ) { - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve - while (curve_it2 != path_it->end_default()) - { - /* Put marker between curve_it1 and curve_it2. - * Loop to end_default (so including closing segment), because when a path is closed, - * there should be a midpoint marker between last segment and closing straight line segment */ - Geom::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); - - if (shape->marker[SP_MARKER_LOC_MID]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC_MID], tr, style, ctx); + } + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + if (shape->marker[i]) { + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there + { + Geom::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front())); + sp_shape_print_invoke_marker_printing(shape->marker[i], tr, style, ctx); } - if (shape->marker[SP_MARKER_LOC]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC], tr, style, ctx); + // MID position + if ( path_it->size_default() > 1) { + Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve + Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve + while (curve_it2 != path_it->end_default()) + { + /* Put marker between curve_it1 and curve_it2. + * Loop to end_default (so including closing segment), because when a path is closed, + * there should be a midpoint marker between last segment and closing straight line segment */ + Geom::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); + + sp_shape_print_invoke_marker_printing(shape->marker[i], tr, style, ctx); + + ++curve_it1; + ++curve_it2; + } + } + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); + Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); + sp_shape_print_invoke_marker_printing(shape->marker[i], tr, style, ctx); } - - ++curve_it1; - ++curve_it2; } } + } + // END marker + if ( shape->marker[SP_MARKER_LOC_END] || shape->marker[SP_MARKER_LOC]) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; - if ( shape->marker[SP_MARKER_LOC_END] || shape->marker[SP_MARKER_LOC]) { - /* Get reference to last curve in the path. - * For moveto-only path, this returns the "closing line segment". */ - unsigned int index = path_it->size_default(); - if (index > 0) { - index--; - } - Geom::Curve const &lastcurve = (*path_it)[index]; - - Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); + Geom::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve); - if (shape->marker[SP_MARKER_LOC_END]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC_END], tr, style, ctx); - } - if (shape->marker[SP_MARKER_LOC]) { - sp_shape_print_invoke_marker_printing(shape->marker[SP_MARKER_LOC], tr, style, ctx); + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if (shape->marker[i]) { + sp_shape_print_invoke_marker_printing(shape->marker[i], tr, style, ctx); } } } @@ -863,15 +946,17 @@ int sp_shape_number_of_markers (SPShape *shape, int type) { Geom::PathVector const & pathv = shape->curve->get_pathvector(); + if (pathv.size() == 0) { + return 0; + } switch(type) { case SP_MARKER_LOC: { if ( shape->marker[SP_MARKER_LOC] ) { - guint n = 2*pathv.size(); + guint n = 0; for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - n += path_it->size(); - n += path_it->closed() ? 1 : 0; + n += path_it->size_default() + 1; } return n; } else { @@ -879,17 +964,17 @@ sp_shape_number_of_markers (SPShape *shape, int type) } } case SP_MARKER_LOC_START: - return shape->marker[SP_MARKER_LOC_START] ? pathv.size() : 0; + // there is only a start marker on the first path of a pathvector + return shape->marker[SP_MARKER_LOC_START] ? 1 : 0; case SP_MARKER_LOC_MID: { if ( shape->marker[SP_MARKER_LOC_MID] ) { - guint n = 0; + guint n = 0; for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - n += path_it->size(); - n += path_it->closed() ? 1 : 0; + n += path_it->size_default() + 1; } - return n; + return n - 2; // minus the start and end marker. } else { return 0; } @@ -897,7 +982,8 @@ sp_shape_number_of_markers (SPShape *shape, int type) case SP_MARKER_LOC_END: { - return shape->marker[SP_MARKER_LOC_END] ? pathv.size() : 0; + // there is only an end marker on the last path of a pathvector + return shape->marker[SP_MARKER_LOC_END] ? 1 : 0; } default: diff --git a/src/sp-spiral.cpp b/src/sp-spiral.cpp index 71906fcc0..629715332 100644 --- a/src/sp-spiral.cpp +++ b/src/sp-spiral.cpp @@ -20,6 +20,7 @@ #include "svg/svg.h" #include "attributes.h" #include <2geom/bezier-utils.h> +#include <2geom/pathvector.h> #include "display/curve.h" #include <glibmm/i18n.h> #include "xml/repr.h" @@ -418,11 +419,23 @@ sp_spiral_fit_and_draw (SPSpiral const *spiral, static void sp_spiral_set_shape (SPShape *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"); + if (SP_OBJECT_REPR(shape)->attribute("d")) { + // unconditionally read the curve from d, if any, to preserve appearance + Geom::PathVector pv = sp_svg_read_pathv(SP_OBJECT_REPR(shape)->attribute("d")); + SPCurve *cold = new SPCurve(pv); + sp_shape_set_curve_insync (shape, cold, TRUE); + cold->unref(); + } + return; + } + Geom::Point darray[SAMPLE_SIZE + 1]; double t; - SPSpiral *spiral = SP_SPIRAL(shape); - SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG); SPCurve *c = new SPCurve (); diff --git a/src/sp-star.cpp b/src/sp-star.cpp index 3c8754a11..9cffd952c 100644 --- a/src/sp-star.cpp +++ b/src/sp-star.cpp @@ -28,6 +28,8 @@ #include "xml/repr.h" #include "document.h" +#include <2geom/pathvector.h> + #include "sp-star.h" static void sp_star_class_init (SPStarClass *klass); @@ -174,7 +176,7 @@ sp_star_set (SPObject *object, unsigned int key, const gchar *value) case SP_ATTR_SODIPODI_SIDES: if (value) { star->sides = atoi (value); - star->sides = CLAMP (star->sides, 3, 1024); + star->sides = NR_CLAMP(star->sides, 3, 1024); } else { star->sides = 5; } @@ -428,6 +430,21 @@ sp_star_set_shape (SPShape *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 + // by disabling the entire stack (including the shape LPE) + if (sp_lpe_item_has_broken_path_effect(SP_LPE_ITEM(shape))) { + g_warning ("The star shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as star will remove the bad LPE"); + if (SP_OBJECT_REPR(shape)->attribute("d")) { + // unconditionally read the curve from d, if any, to preserve appearance + Geom::PathVector pv = sp_svg_read_pathv(SP_OBJECT_REPR(shape)->attribute("d")); + SPCurve *cold = new SPCurve(pv); + sp_shape_set_curve_insync (shape, cold, TRUE); + cold->unref(); + } + return; + } + SPCurve *c = new SPCurve (); gint sides = star->sides; @@ -500,7 +517,7 @@ sp_star_set_shape (SPShape *shape) bool success = sp_lpe_item_perform_path_effect(SP_LPE_ITEM (shape), c_lpe); if (success) { sp_shape_set_curve_insync (shape, c_lpe, TRUE); - } + } c_lpe->unref(); } c->unref(); @@ -512,13 +529,13 @@ sp_star_position_set (SPStar *star, gint sides, Geom::Point center, gdouble r1, g_return_if_fail (star != NULL); g_return_if_fail (SP_IS_STAR (star)); - star->sides = CLAMP (sides, 3, 1024); + star->sides = NR_CLAMP(sides, 3, 1024); star->center = center; star->r[0] = MAX (r1, 0.001); if (isflat == false) { - star->r[1] = CLAMP (r2, 0.0, star->r[0]); + star->r[1] = NR_CLAMP(r2, 0.0, star->r[0]); } else { - star->r[1] = CLAMP ( r1*cos(M_PI/sides) ,0.0, star->r[0] ); + star->r[1] = NR_CLAMP( r1*cos(M_PI/sides) ,0.0, star->r[0] ); } star->arg[0] = arg1; star->arg[1] = arg2; @@ -587,3 +604,13 @@ sp_star_get_xy (SPStar *star, SPStarPoint point, gint index, bool randomized) } } +/* + 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/sp-text.cpp b/src/sp-text.cpp index 61947311c..0d3fd791b 100644 --- a/src/sp-text.cpp +++ b/src/sp-text.cpp @@ -937,11 +937,11 @@ void TextTagAttributes::addToRotate(unsigned index, double delta) SVGLength zero_length; zero_length = 0.0; - if (attributes.rotate.size() < index + 1) { + if (attributes.rotate.size() < index + 2) { if (attributes.rotate.empty()) - attributes.rotate.resize(index + 1, zero_length); + attributes.rotate.resize(index + 2, zero_length); else - attributes.rotate.resize(index + 1, attributes.rotate.back()); + attributes.rotate.resize(index + 2, attributes.rotate.back()); } attributes.rotate[index] = mod360(attributes.rotate[index].computed + delta); } diff --git a/src/spiral-context.cpp b/src/spiral-context.cpp index 2f65b0ba9..3825f74c7 100644 --- a/src/spiral-context.cpp +++ b/src/spiral-context.cpp @@ -46,6 +46,7 @@ static void sp_spiral_context_class_init(SPSpiralContextClass * klass); static void sp_spiral_context_init(SPSpiralContext *spiral_context); static void sp_spiral_context_dispose(GObject *object); static void sp_spiral_context_setup(SPEventContext *ec); +static void sp_spiral_context_finish(SPEventContext *ec); static void sp_spiral_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val); static gint sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event); @@ -87,6 +88,7 @@ sp_spiral_context_class_init(SPSpiralContextClass *klass) object_class->dispose = sp_spiral_context_dispose; event_context_class->setup = sp_spiral_context_setup; + event_context_class->finish = sp_spiral_context_finish; event_context_class->set = sp_spiral_context_set; event_context_class->root_handler = sp_spiral_context_root_handler; } @@ -114,6 +116,20 @@ sp_spiral_context_init(SPSpiralContext *spiral_context) new (&spiral_context->sel_changed_connection) sigc::connection(); } +static void sp_spiral_context_finish(SPEventContext *ec) +{ + SPSpiralContext *sc = SP_SPIRAL_CONTEXT(ec); + SPDesktop *desktop = ec->desktop; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + sp_spiral_finish(sc); + sc->sel_changed_connection.disconnect(); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } +} + static void sp_spiral_context_dispose(GObject *object) { @@ -221,7 +237,6 @@ sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event) if (event->button.button == 1 && !event_context->space_panning) { dragging = TRUE; - sp_event_context_snap_window_open(event_context); sc->center = Inkscape::setup_for_drag_start(desktop, event_context, event); SnapManager &m = desktop->namedview->snap_manager; @@ -270,7 +285,7 @@ sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event) event_context->xp = event_context->yp = 0; if (event->button.button == 1 && !event_context->space_panning) { dragging = FALSE; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the spiral sp_spiral_finish(sc); @@ -323,7 +338,7 @@ sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event) case GDK_Escape: if (dragging) { dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); // if drawing, cancel, otherwise pass it up for deselecting sp_spiral_cancel(sc); ret = TRUE; @@ -335,7 +350,7 @@ sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event) sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the spiral sp_spiral_finish(sc); diff --git a/src/splivarot.cpp b/src/splivarot.cpp index feba2cab6..085dfeda0 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -856,19 +856,32 @@ sp_selected_path_outline(SPDesktop *desktop) SPShape *shape = SP_SHAPE(item); Geom::PathVector const & pathv = curve->get_pathvector(); - for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { - for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START - if ( SPObject *marker_obj = shape->marker[i] ) { + + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( SPObject *marker_obj = shape->marker[i] ) { + Geom::Matrix const m (sp_shape_marker_get_transform_at_start(pathv.front().front())); + sp_selected_path_outline_add_marker( marker_obj, m, + Geom::Scale(i_style->stroke_width.computed), transform, + g_repr, xml_doc, doc ); + } + } + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + SPObject *midmarker_obj = shape->marker[i]; + if (!midmarker_obj) continue; + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there + { Geom::Matrix const m (sp_shape_marker_get_transform_at_start(path_it->front())); - sp_selected_path_outline_add_marker( marker_obj, m, + sp_selected_path_outline_add_marker( midmarker_obj, m, Geom::Scale(i_style->stroke_width.computed), transform, g_repr, xml_doc, doc ); } - } - - for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID - SPObject *midmarker_obj = shape->marker[i]; - if ( midmarker_obj && (path_it->size_default() > 1) ) { + // MID position + if (path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) @@ -886,25 +899,34 @@ sp_selected_path_outline(SPDesktop *desktop) ++curve_it2; } } - } - - for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END - if ( SPObject *marker_obj = shape->marker[i] ) { - /* Get reference to last curve in the path. - * For moveto-only path, this returns the "closing line segment". */ - unsigned int index = path_it->size_default(); - if (index > 0) { - index--; - } - Geom::Curve const &lastcurve = (*path_it)[index]; - + // END position + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve); - sp_selected_path_outline_add_marker( marker_obj, m, + sp_selected_path_outline_add_marker( midmarker_obj, m, Geom::Scale(i_style->stroke_width.computed), transform, g_repr, xml_doc, doc ); } } } + // END marker + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( SPObject *marker_obj = shape->marker[i] ) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; + + Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve); + sp_selected_path_outline_add_marker( marker_obj, m, + Geom::Scale(i_style->stroke_width.computed), transform, + g_repr, xml_doc, doc ); + } + } selection->add(g_repr); diff --git a/src/star-context.cpp b/src/star-context.cpp index c5eff3c6a..3d6825e31 100644 --- a/src/star-context.cpp +++ b/src/star-context.cpp @@ -34,6 +34,7 @@ #include "desktop.h" #include "desktop-style.h" #include "message-context.h" +#include "libnr/nr-macros.h" #include "pixmaps/cursor-star.xpm" #include "sp-metrics.h" #include <glibmm/i18n.h> @@ -51,6 +52,7 @@ static void sp_star_context_init (SPStarContext * star_context); static void sp_star_context_dispose (GObject *object); static void sp_star_context_setup (SPEventContext *ec); +static void sp_star_context_finish(SPEventContext *ec); static void sp_star_context_set (SPEventContext *ec, Inkscape::Preferences::Entry *val); static gint sp_star_context_root_handler (SPEventContext *ec, GdkEvent *event); @@ -91,6 +93,7 @@ sp_star_context_class_init (SPStarContextClass * klass) object_class->dispose = sp_star_context_dispose; event_context_class->setup = sp_star_context_setup; + event_context_class->finish = sp_star_context_finish; event_context_class->set = sp_star_context_set; event_context_class->root_handler = sp_star_context_root_handler; } @@ -118,6 +121,21 @@ sp_star_context_init (SPStarContext * star_context) new (&star_context->sel_changed_connection) sigc::connection(); } +static void sp_star_context_finish(SPEventContext *ec) +{ + SPStarContext *sc = SP_STAR_CONTEXT(ec); + SPDesktop *desktop = ec->desktop; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + sp_star_finish(sc); + sc->sel_changed_connection.disconnect(); + + if (((SPEventContextClass *) parent_class)->finish) { + ((SPEventContextClass *) parent_class)->finish(ec); + } +} + + static void sp_star_context_dispose (GObject *object) { @@ -204,9 +222,9 @@ sp_star_context_set (SPEventContext *ec, Inkscape::Preferences::Entry *val) Glib::ustring path = val->getEntryName(); if (path == "magnitude") { - sc->magnitude = CLAMP (val->getInt(5), 3, 1024); + sc->magnitude = NR_CLAMP(val->getInt(5), 3, 1024); } else if (path == "proportion") { - sc->proportion = CLAMP (val->getDouble(0.5), 0.01, 2.0); + sc->proportion = NR_CLAMP(val->getDouble(0.5), 0.01, 2.0); } else if (path == "isflatsided") { sc->isflatsided = val->getBool(); } else if (path == "rounded") { @@ -235,7 +253,6 @@ static gint sp_star_context_root_handler(SPEventContext *event_context, GdkEvent if (event->button.button == 1 && !event_context->space_panning) { dragging = TRUE; - sp_event_context_snap_window_open(event_context); sc->center = Inkscape::setup_for_drag_start(desktop, event_context, event); @@ -282,7 +299,7 @@ static gint sp_star_context_root_handler(SPEventContext *event_context, GdkEvent event_context->xp = event_context->yp = 0; if (event->button.button == 1 && !event_context->space_panning) { dragging = FALSE; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the star sp_star_finish (sc); @@ -335,7 +352,7 @@ static gint sp_star_context_root_handler(SPEventContext *event_context, GdkEvent case GDK_Escape: if (dragging) { dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); // if drawing, cancel, otherwise pass it up for deselecting sp_star_cancel(sc); ret = TRUE; @@ -346,7 +363,7 @@ static gint sp_star_context_root_handler(SPEventContext *event_context, GdkEvent sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); dragging = false; - sp_event_context_snap_window_closed(event_context); + sp_event_context_discard_delayed_snap_event(event_context); if (!event_context->within_tolerance) { // we've been dragging, finish the star sp_star_finish(sc); diff --git a/src/style.cpp b/src/style.cpp index dd8169282..0b946f348 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -711,11 +711,14 @@ sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr) sp_style_read_dash(style, val); } } + if (!style->stroke_dashoffset_set) { - /* fixme */ val = repr->attribute("stroke-dashoffset"); if (sp_svg_number_read_d(val, &style->stroke_dash.offset)) { style->stroke_dashoffset_set = TRUE; + } else if (val && !strcmp(val, "inherit")) { + style->stroke_dashoffset_set = TRUE; + style->stroke_dashoffset_inherit = TRUE; } else { style->stroke_dashoffset_set = FALSE; } @@ -1146,9 +1149,11 @@ sp_style_merge_property(SPStyle *style, gint id, gchar const *val) break; case SP_PROP_STROKE_DASHOFFSET: if (!style->stroke_dashoffset_set) { - /* fixme */ if (sp_svg_number_read_d(val, &style->stroke_dash.offset)) { style->stroke_dashoffset_set = TRUE; + } else if (val && !strcmp(val, "inherit")) { + style->stroke_dashoffset_set = TRUE; + style->stroke_dashoffset_inherit = TRUE; } else { style->stroke_dashoffset_set = FALSE; } @@ -1526,11 +1531,7 @@ sp_style_merge_from_parent(SPStyle *const style, SPStyle const *const parent) style->stroke_miterlimit.value = parent->stroke_miterlimit.value; } - if (!style->stroke_dasharray_set && parent->stroke_dasharray_set) { - /** \todo - * This code looks wrong. Why does the logic differ from the - * above properties? Similarly dashoffset below. - */ + if (!style->stroke_dasharray_set || style->stroke_dasharray_inherit) { style->stroke_dash.n_dash = parent->stroke_dash.n_dash; if (style->stroke_dash.n_dash > 0) { style->stroke_dash.dash = g_new(gdouble, style->stroke_dash.n_dash); @@ -1538,7 +1539,7 @@ sp_style_merge_from_parent(SPStyle *const style, SPStyle const *const parent) } } - if (!style->stroke_dashoffset_set && parent->stroke_dashoffset_set) { + if (!style->stroke_dashoffset_set || style->stroke_dashoffset_inherit) { style->stroke_dash.offset = parent->stroke_dash.offset; } @@ -2100,10 +2101,11 @@ sp_style_merge_from_dying_parent(SPStyle *const style, SPStyle const *const pare } /* Note: this will need length handling once dasharray_offset supports units. */ - if (!style->stroke_dashoffset_set && parent->stroke_dashoffset_set) { + if ((!style->stroke_dashoffset_set || style->stroke_dashoffset_inherit) && parent->stroke_dashoffset_set && !parent->stroke_dashoffset_inherit) { style->stroke_dash.offset = parent->stroke_dash.offset; style->stroke_dashoffset_set = parent->stroke_dashoffset_set; - /* fixme: we don't currently allow stroke-dashoffset to be `inherit'. TODO: Try to + style->stroke_dashoffset_inherit = parent->stroke_dashoffset_inherit; + /* TODO: Try to * represent it as a normal SPILength; though will need to do something about existing * users of stroke_dash.offset and stroke_dashoffset_set. */ } @@ -2326,9 +2328,13 @@ sp_style_write_string(SPStyle const *const style, guint const flags) /** \todo fixme: */ if (style->stroke_dashoffset_set) { - Inkscape::CSSOStringStream os; - os << "stroke-dashoffset:" << style->stroke_dash.offset << ";"; - p += g_strlcpy(p, os.str().c_str(), c + BMAX - p); + if (style->stroke_dashoffset_inherit) { + p += g_snprintf(p, c + BMAX - p, "stroke-dashoffset:inherit;"); + } else { + Inkscape::CSSOStringStream os; + os << "stroke-dashoffset:" << style->stroke_dash.offset << ";"; + p += g_strlcpy(p, os.str().c_str(), c + BMAX - p); + } } else if (flags == SP_STYLE_FLAG_ALWAYS) { p += g_snprintf(p, c + BMAX - p, "stroke-dashoffset:0;"); } @@ -2478,9 +2484,13 @@ sp_style_write_difference(SPStyle const *const from, SPStyle const *const to) } /* fixme: */ if (from->stroke_dashoffset_set) { - Inkscape::CSSOStringStream os; - os << "stroke-dashoffset:" << from->stroke_dash.offset << ";"; - p += g_strlcpy(p, os.str().c_str(), c + BMAX - p); + if (from->stroke_dashoffset_inherit) { + p += g_snprintf(p, c + BMAX - p, "stroke-dashoffset:inherit;"); + } else { + Inkscape::CSSOStringStream os; + os << "stroke-dashoffset:" << from->stroke_dash.offset << ";"; + p += g_strlcpy(p, os.str().c_str(), c + BMAX - p); + } } p += sp_style_write_iscale24(p, c + BMAX - p, "stroke-opacity", &from->stroke_opacity, &to->stroke_opacity, SP_STYLE_FLAG_IFDIFF); } @@ -2559,6 +2569,9 @@ sp_style_clear(SPStyle *style) g_free(style->stroke_dash.dash); } + style->stroke_dasharray_inherit = FALSE; + style->stroke_dashoffset_inherit = FALSE; + /** \todo fixme: Do that text manipulation via parents */ SPObject *object = style->object; SPDocument *document = style->document; diff --git a/src/style.h b/src/style.h index d5ccc4901..9a2c72f16 100644 --- a/src/style.h +++ b/src/style.h @@ -336,6 +336,7 @@ struct SPStyle { unsigned stroke_dasharray_set : 1; unsigned stroke_dasharray_inherit : 1; unsigned stroke_dashoffset_set : 1; + unsigned stroke_dashoffset_inherit : 1; /** stroke-opacity */ SPIScale24 stroke_opacity; diff --git a/src/svg-view.cpp b/src/svg-view.cpp index bd86d5477..bd46dd17a 100644 --- a/src/svg-view.cpp +++ b/src/svg-view.cpp @@ -114,7 +114,7 @@ SPSVGView::mouseover() { GdkCursor *cursor = gdk_cursor_new(GDK_HAND2); gdk_window_set_cursor(GTK_WIDGET(SP_CANVAS_ITEM(_drawing)->canvas)->window, cursor); - gdk_cursor_destroy(cursor); + gdk_cursor_unref(cursor); } void diff --git a/src/svg/ftos.cpp b/src/svg/ftos.cpp deleted file mode 100644 index c468b4c63..000000000 --- a/src/svg/ftos.cpp +++ /dev/null @@ -1,485 +0,0 @@ -/* ////////////////////////////////////////////////////////////////////// -// ftos.cc -// -// Copyright (c) 1996-2003 Bryce W. Harrington [bryce at osdl dot org] -// -//----------------------------------------------------------------------- -// License: This code may be used by anyone for any purpose -// so long as the copyright notices and this license -// statement remains attached. -//----------------------------------------------------------------------- -// -// string ftos(double val[, char mode[, int sigfig[, int precision[, int options]]]]) -// -// DESCRIPTION -// This routine is intended to replace the typical use of sprintf for -// converting floating point numbers into strings. -// -// To one-up sprintf, an additional mode was created - 'h' mode - -// which produces numbers in 'engineering notation' - exponents are -// always shown in multiples of 3. To non-engineers this mode is -// probably irrelevant, but for engineers (and scientists) it is SOP. -// -// One other new feature is an option to use 'x10^' instead of the -// conventional 'E' for exponental notation. This is entirely for -// aesthetics since numbers in the 'x10^' form cannot be used as -// inputs for most programs. -// -// For most cases, the routine can simply be used with the defaults -// and acceptable results will be produced. No fill zeros or trailing -// zeros are shown, and exponential notation is only used for numbers -// greater than 1e6 or less than 1e-3. -// -// The one area where sprintf may surpass this routine is in width control. -// No provisions are made in this routine to restrict a number to a -// certain number of digits (thus allowing the number to be constrained -// to an 8 space column, for instance.) Along with this, it does not -// support pre-padding a number with zeros (e.g., '5' -> '0005') and will -// not post-pad a number with spaces (i.e., allow left-justification.) -// -// If width control is this important, then the user will probably want to -// use the stdio routines, which really is well suited for outputting -// columns of data with a brief amount of code. -// -// PARAMETERS -// val - number to be converted -// mode - can be one of four possible values. Default is 'g' -// -// e: Produces numbers in scientific notation. One digit -// is shown on the left side of the decimal, the rest -// on the right, and the exponential is always shown. -// EXAMPLE: 1.04e-4 -// -// f: Produces numbers with fixed format. Number is shown -// exact, with no exponent. -// EXAMPLE: 0.000104 -// -// g: If val is greater than 1e6 or less than 1e-3 it will -// be shown in 'e' format, otherwise 'f' format will be -// used. -// -// h: Produces numbers in engineering format. Result is -// identical to 'f' format for numbers between 1 and -// 1e3, otherwise, the number is shown such that it -// always begins with a nonzero digit on the left side -// (unless number equals zero), and the exponential is -// a multiple of 3. -// EXAMPLE: 104e-6 -// -// If the mode is expressed as a capital letter (e.g., 'F') -// then the exponential part of the number will also be -// capitalized (e.g., '1E6' or '1X10^6'.) -// -// sigfig - the number of significant figures. These are the digits -// that are "retained". For example, the following numbers -// all have four sigfigs: -// 1234 12.34 0.0001234 1.234e-10 -// the last digit shown will be rounded in the standard -// manner (down if the next digit is less than 5, up otherwise.) -// -// precision - the number of digits to show to the right of the decimal. -// For example, all of the following numbers have precisions -// of 2: -// 1234.00 12.34 0.00 1.23e-10 123.40e-12 -// -// options - several options are allowed to control the look of the -// output. -// -// FORCE_DECIMAL - require the decimal point to be shown for -// numbers that do not have any fractional digits (or that -// have a precision set to zero) -// EXAMPLE: 1.e6 -// FORCE_EXP_ZERO - pad the 10's zero in exponent if necessary -// EXAMPLE: 1e06 -// FORCE_HUNDRED_EXP_ZERO - pad the 100's zero in exponent if -// necessary. Also pads 10's zero in exponent if necessary. -// EXAMPLE: 1e006 -// FORCE_EXP_PLUS - show the '+' in the exponent if exponent -// is used. -// EXAMPLE: 1e+6 -// FORCE_EXP - force the output to display the exponent -// EXAMPLE: 0e0 -// FORCE_X10 - use x10^ instead of E -// EXAMPLE: 1x10^6 -// FORCE_PLUS - force output of the '+' for positive numbers -// EXAMPLE: +1e6 -// -// Options can be combined using the usual OR method. For -// example, -// -// ftos(123.456, 'f', -1, -1, FORCE_PLUS | FORCE_X10 | FORCE_EXP) -// -// gives "+123.456x10^0" -// -// RETURN VALUE -// The string representation of the number is returned from the routine. -// The ANSI C++ Standard "string" class was used for several important -// reasons. First, because the string class manages it's own space, the -// ftos routine does not need to concern itself with writing to unallocated -// areas of memory or with handling memory reallocation internally. Second, -// it allows return of an object, not a pointer to an object; this may not -// be as efficient, but it is cleaner and safer than the alternative. Third, -// the routine's return value can be directly assigned to a variable, i.e. -// string var = ftos(3.1415); -// which makes code much easier to comprehend and modify. -// -// Internally, the ftos routine uses fairly typical string operators (=, +=, -// +, etc.) which pretty much any other flavor of string class will define as -// well. Thus if one does not have access to the ANSI C++ Standard string -// class, the user can substitute another with little difficulty. (If the -// alternate class is not named "string" then redefine "string" to whatever -// you wish to use. For example, -// #define string CString -// -// November 1996 - Bryce Harrington -// Created ftoa and ftos -// -// December 1996 - Bryce Harrington -// Added engineering notation mode, added sigfig capability, added -// significant debug code, added options, thoroughly debugged and -// tested the code. -// -// -// June 1999 - Bryce Harrington -// Modified to run on Linux for WorldForge -// -// March 2003 - Bryce Harrington -// Removed DTAG() macros - use of fprintf(stderr,...) instead -// Broke out round/itos/ftos into separate files -// Removed curses bits -// -/////////////////////////////////////////////////////////////////////// */ - -#include <string> - -// This is the routine used for converting a floating point into a string -// This may be included in stdlib.h on some systems and may conflict. -// Let me know your system & etc. so I can properly #ifdef this, but -// try commenting the following four lines out if you run into conflicts. -// extern "C" { -// char* -// ecvt (double val, size_t ndigit, int *decpt, int *sign); -// } - -using namespace std; - -#ifndef HAS_ECVT -#include <cstdio> -#include <glib.h> -#endif - - -#include "ftos.h" - -#include <iostream> - -// This routine counts from the end of a string like '10229000' to find the index -// of the first non-'0' character (5 would be returned for the above number.) -int countDigs(char *p) -{ - int length =0; - while (*(p+length)!='\0') length++; // Count total length - while (length>0 && *(p+length-1)=='0') length--; // Scan backwards for a non-'0' - return length; -} - -// This routine determines how many digits make up the left hand -// side of the number if the abs value of the number is greater than 1, or the -// digits that make up the right hand side if the abs value of the number -// is between 0 and 1. Returns 1 if v==0. Return value is positive for numbers -// greater than or equal to 1, negative for numbers less than 0.1, and zero for -// numbers between 0.1 and 1. -int countLhsDigits(double v) -{ - if (v<0) v = -v; // Take abs value - else if (v==0) return 1; // Special case if v==0 - - int n=0; - for (; v<0.1; v*=10) // Count digits on right hand side (l.t. 0.1) - { n--; } - for (; v>=1; v/=10) // Count digits on left hand side (g.e. 1.0) - { n++; } - return n; -} - -// This is the routine that does the work of converting the number into a string. -string ftos(double val, char mode, int sigfig, int precision, int options) -{ - // Parse the options to a more usable form - // These options allow the user to control some of the ornaments on the - // number that is output. By default they are all false. Turning them - // on helps to "fix" the format of the number so it lines up in columns - // better. - // - require the decimal point to be shown for numbers that do not have - // any fractional digits (or that have a precision set to zero - bool forceDecimal = (options & FORCE_DECIMAL); - // - show the 10's and 100's zero in exponent - bool forceExpZero = (options & FORCE_EXP_ZERO); - bool forceHundredExpZero = (options & FORCE_HUNDRED_EXP_ZERO); - // - show the '+' in the exponent if exponent is used - bool forceExpPlus = (options & FORCE_EXP_PLUS); - // - force the output to display the exponent - bool forceExponent = (options & FORCE_EXP); - // - use x10^ instead of E - bool forcex10 = (options & FORCE_X10); - // - force output of the '+' for positive numbers - bool forcePlus = (options & FORCE_PLUS); - -#ifdef DEBUG - fprintf(stderr, "Options: "); - fprintf(stderr, " %4s = %s ", "x10", (forcex10 ? "on" : "off" )); - fprintf(stderr, " %4s = %s ", ".", (forceDecimal ? "on" : "off" )); - fprintf(stderr, " %4s = %s ", "e0", (forceExpZero ? "on" : "off" )); - fprintf(stderr, " %4s = %s ", "e00", (forceHundredExpZero ? "on" : "off" )); - fprintf(stderr, " %4s = %s ", "e+", (forceExpPlus ? "on" : "off" )); - fprintf(stderr, " %4s = %s ", "e", (forceExponent ? "on" : "off" )); - fprintf(stderr, " %4s = %s \n", "+#", (forcePlus ? "on" : "off" )); -#endif - - // - exponent usage - bool useExponent = false; - - // Determine the case for the 'e' (if used) - char E = (forcex10)? 'x' : 'e'; - if (g_ascii_isupper(mode)) { - E = g_ascii_toupper(E); - mode = g_ascii_tolower(mode); - } - - // Determine how many decimals we're interested in - int L = countLhsDigits(val); - -#ifdef DEBUG - fprintf(stderr, "*** L is %s\n", itos(L).c_str()); -#endif - - int count = 0; - if (sigfig==0) // bad input - don't want any sigfigs??!! - return ""; - else if (precision>=0) { // Use fixed number of decimal places - count = precision; - if (mode == 'e') count += 1; - else if (mode == 'f') count += L; - else if (mode == 'g') count += (L>6 || L<-3)? 1 : L; - else if (mode == 'h') count += (L>0)? ((L-1)%3+1) : (L%3+3); - if (sigfig>0) count = (sigfig > count)? count : sigfig; // Use sigfig # if it means more decimal places - } - else if (sigfig>0) // Just use sigfigs - count = sigfig; - else // prec < 0 and sigfig < 0 - count = 10; -#ifdef DEBUG - fprintf(stderr, "*** count is %s\n", itos(count).c_str()); -#endif - - // Get number's string rep, sign, and exponent - int sign = 0; - int decimal=0; - -#ifdef HAS_ECVT - char *p = ecvt(val, count, &decimal, &sign); -#else - char *p = (char *) g_strdup_printf("%.0f", val); - // asprintf(&p, "%.0f", val); -#endif - -#ifdef DEBUG - fprintf(stderr, "*** string rep is %s\n", p); - fprintf(stderr, "*** decimal is %s\n", itos(decimal).c_str()); - fprintf(stderr, "*** sign is %s\n", itos(sign).c_str()); -#endif - - // Count the number of relevant digits in the resultant number - int dig = countDigs(p); - if (dig < sigfig) dig = sigfig; - -#ifdef DEBUG - fprintf(stderr, "*** digs is %s\n", itos(dig).c_str()); -#endif - - // Determine number of digits to put on left side of the decimal point - int lhs=0; - // For 'g' mode, decide whether to use 'e' or 'f' format. - if (mode=='g') mode = (decimal>6 || decimal<-3)? 'e' : 'f'; - switch (mode) { - case 'e': - lhs = 1; // only need one char on left side - useExponent = true; // force exponent use - break; - - case 'f': - lhs = (decimal<1)? 1 : decimal; - // use one char on left for num < 1, - // otherwise, use the number of decimal places. - useExponent = false; // don't want exponent for 'f' format - break; - - case 'h': - if (val==0.0) // special case for if value is zero exactly. - lhs = 0; // this prevents code from returning '000.0' - else - lhs = (decimal<=0)? (decimal)%3 + 3 : (decimal-1)%3+1; - useExponent = !(lhs==decimal); // only use exponent if we really need it - break; - - default: - return "**bad mode**"; - } - -#ifdef DEBUG - fprintf(stderr, "*** lhs is %s\n", itos(lhs).c_str()); -#endif - - // Figure out the number of digits to show in the right hand side - int rhs=0; - if (precision>=0) - rhs = precision; - else if (val == 0.0) - rhs = 0; - else if (useExponent || decimal>0) - rhs = dig-lhs; - else - rhs = dig-decimal; - - // can't use a negative rhs value, so turn it to zero if that is the case - if (rhs<0) rhs = 0; - -#ifdef DEBUG - fprintf(stderr, "*** rhs is", itos(rhs).c_str()); -#endif - - // Determine the exponent - int exponent = decimal - lhs; - if (val==0.0) exponent=0; // prevent zero from getting an exponent -#ifdef DEBUG - fprintf(stderr, "*** exponent is %s\n", itos(exponent).c_str()); -#endif - - string ascii; - - // output the sign - if (sign) ascii += "-"; - else if (forcePlus) ascii += "+"; - - // output the left hand side - if (!useExponent && decimal<=0) // if fraction, put the 0 out front - ascii += '0'; - else // is either exponential or >= 1, so write the lhs - for (; lhs>0; lhs--) - ascii += (*p)? *p++ : int('0'); // now fill in the numbers before decimal - -#ifdef DEBUG - fprintf(stderr, "*** ascii + sign + lhs is %s\n", ascii.c_str()); -#endif - - // output the decimal point - if (forceDecimal || rhs>0) - ascii += '.'; - - // output the right hand side - if (!useExponent && rhs>0) // first fill in zeros after dp and before numbers - while (decimal++ <0 && rhs-->0) - ascii += '0'; - for (; rhs>0 ; rhs--) // now fill in the numbers after decimal - ascii += (*p)? *p++ : int('0'); - -#ifdef DEBUG - fprintf(stderr, "*** ascii + . + rhs is %s\n", ascii.c_str()); -#endif - - if (forceExponent || useExponent) // output the entire exponent if required - { - ascii += E; // output the E or X - if (forcex10) ascii += "10^"; // if using 'x10^' format, output the '10^' part - - // output the exponent's sign - if (exponent < 0) { // Negative exponent - exponent = -exponent; // make exponent positive if needed - ascii += '-'; // output negative sign - } - else if (forceExpPlus) // We only want the '+' if it is asked for explicitly - ascii += '+'; - - // output the exponent - if (forceHundredExpZero || exponent >= 100) - ascii += ( (exponent/100) % 10 + '0' ); - if (forceHundredExpZero || forceExpZero || exponent >= 10) - ascii += ( (exponent/10) % 10 + '0' ); - ascii += ( exponent % 10 + '0' ); - -#ifdef DEBUG - fprintf(stderr, "*** ascii + exp is %s\n", ascii.c_str()); -#endif - } - -#ifdef DEBUG - fprintf(stderr, "*** End of ftos with ascii = ", ascii.c_str()); -#endif - /* finally, we can return */ - return ascii; -} - -#ifdef TESTFTOS - -int main() -{ - cout << "Normal (g): " << endl; - cout << "1.0 = " << ftos(1.0) << endl; - cout << "42 = " << ftos(42) << endl; - cout << "3.141 = " << ftos(3.141) << endl; - cout << "0.01 = " << ftos(0.01) << endl; - cout << "1.0e7 = " << ftos(1.0e7) << endl; - cout << endl; - - cout << "Scientific (e): " << endl; - cout << "1.0 = " << ftos(1.0, 'e') << endl; - cout << "42 = " << ftos(42, 'e') << endl; - cout << "3.141 = " << ftos(3.141, 'e') << endl; - cout << "0.01 = " << ftos(0.01, 'e') << endl; - cout << "1.0e7 = " << ftos(1.0e7, 'e') << endl; - cout << endl; - - cout << "Fixed (f): " << endl; - cout << "1.0 = " << ftos(1.0, 'f') << endl; - cout << "42 = " << ftos(42, 'f') << endl; - cout << "3.141 = " << ftos(3.141, 'f') << endl; - cout << "0.01 = " << ftos(0.01, 'f') << endl; - cout << "1.0e7 = " << ftos(1.0e7, 'f') << endl; - cout << endl; - - cout << "Engineering (h): " << endl; - cout << "1.0 = " << ftos(1.0, 'h') << endl; - cout << "42 = " << ftos(42, 'h') << endl; - cout << "3.141 = " << ftos(3.141, 'h') << endl; - cout << "0.01 = " << ftos(0.01, 'h') << endl; - cout << "1.0e7 = " << ftos(1.0e7, 'h') << endl; - cout << endl; - - cout << "Sigfigs: " << endl; - cout << "2 sf = " << ftos(1234, 'g', 2) << " " - << ftos(12.34, 'g', 2) << " " - << ftos(0, 'g', 2) << " " - << ftos(123.4e-11, 'g', 2) << endl; - cout << "4 sf = " << ftos(1234, 'g', 4) << " " - << ftos(12.34, 'g', 4) << " " - << ftos(0, 'g', 4) << " " - << ftos(123.4e-11, 'g', 4) << endl; - cout << "8 sf = " << ftos(1234, 'g', 8) << " " - << ftos(12.34, 'g', 8) << " " - << ftos(0, 'g', 8) << " " - << ftos(123.4e-11, 'g', 8) << endl; - cout << endl; - - cout << "x10 mode: " << endl; - cout << "1234 = " << ftos(1234, 'e', 4, -1, FORCE_X10 | FORCE_EXP) << endl; - cout << "1.01e10 = " << ftos(1.01e10, 'h', -1, -1, FORCE_X10 | FORCE_EXP) << endl; - cout << endl; - - cout << "itos tests..." << endl; - cout << "42 = " << itos(42) << endl; - cout << endl; - - return 0; -} - -#endif // TESTFTOS diff --git a/src/svg/ftos.h b/src/svg/ftos.h deleted file mode 100644 index 888def639..000000000 --- a/src/svg/ftos.h +++ /dev/null @@ -1,54 +0,0 @@ -///////////////////////////////////////////////////////////////////////// -// ftos.h -// -// Copyright (c) 1996 Bryce W. Harrington - bryce@neptune.net -// -//----------------------------------------------------------------------- -// License: This code may be used by anyone for any purpose -// so long as the copyright notices and this license -// statement remains attached. -//----------------------------------------------------------------------- -// Description of file contents -// 1993 - Bryce Harrington -// Created initial ftoa routine -// -// October 1996 - Bryce Harrington -// Created itos from code taken from Kernighan & Ritchie -// _The C Programming Language_ 2nd edition -// -// November 1996 - Bryce Harrington -// Created new ftoa and ftos -// -// July 1999 - Bryce Harrington -// Ported to Linux for use in WorldForge -// -// January 2000 - Karsten Laux klaux@rhrk.uni-kl.de -// added ultos - convering ulong to string -// -///////////////////////////////////////////////////////////////////////// -#ifndef ftos_h -#define ftos_h - -#include <string> - -// ftos routine -const int FORCE_X10 = (1 << 0); -const int FORCE_DECIMAL = (1 << 1); -const int FORCE_EXP_ZERO = (1 << 2); -const int FORCE_HUNDRED_EXP_ZERO = (1 << 3); -const int FORCE_EXP_PLUS = (1 << 4); -const int FORCE_EXP = (1 << 5); -const int FORCE_PLUS = (1 << 6); - -/// -std::string ftos(double val, char mode='g', int sigfig=-1, int precision=-1, int options=0); -/// -std::string itos(int n); -/// -std::string ultos(unsigned long n); -/// -double rround(double x); // use rounding rule -> x to nearest int. -/// -double rround(double x, int k); // round to the kth place - -#endif // ftos_h diff --git a/src/svg/path-string.cpp b/src/svg/path-string.cpp index 0baaf521e..a850d7c10 100644 --- a/src/svg/path-string.cpp +++ b/src/svg/path-string.cpp @@ -129,7 +129,7 @@ void Inkscape::SVG::PathString::State::appendNumber(double v, int precision, int size_t const oldsize = str.size(); str.append(reserve, (char)0); char* begin_of_num = const_cast<char*>(str.data()+oldsize); // Slightly evil, I know (but std::string should be storing its data in one big block of memory, so...) - size_t added = sp_svg_number_write_de(begin_of_num, v, precision, minexp); + size_t added = sp_svg_number_write_de(begin_of_num, reserve, v, precision, minexp); str.resize(oldsize+added); // remove any trailing characters } diff --git a/src/svg/svg-affine.cpp b/src/svg/svg-affine.cpp index 1ff9776e5..91a9fa7e5 100644 --- a/src/svg/svg-affine.cpp +++ b/src/svg/svg-affine.cpp @@ -177,9 +177,9 @@ sp_svg_transform_write(Geom::Matrix const &transform) unsigned p = 0; strcpy (c + p, "scale("); p += 6; - p += sp_svg_number_write_de (c + p, transform[0], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[3], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp ); c[p++] = ')'; c[p] = '\000'; g_assert( p <= sizeof(c) ); @@ -192,9 +192,9 @@ sp_svg_transform_write(Geom::Matrix const &transform) unsigned p = 0; strcpy (c + p, "translate("); p += 10; - p += sp_svg_number_write_de (c + p, transform[4], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[5], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp ); c[p++] = ')'; c[p] = '\000'; g_assert( p <= sizeof(c) ); @@ -204,17 +204,17 @@ sp_svg_transform_write(Geom::Matrix const &transform) unsigned p = 0; strcpy (c + p, "matrix("); p += 7; - p += sp_svg_number_write_de (c + p, transform[0], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[1], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[1], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[2], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[2], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[3], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[4], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[5], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp ); c[p++] = ')'; c[p] = '\000'; g_assert( p <= sizeof(c) ); @@ -226,17 +226,17 @@ sp_svg_transform_write(Geom::Matrix const &transform) unsigned p = 0; strcpy (c + p, "matrix("); p += 7; - p += sp_svg_number_write_de (c + p, transform[0], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[1], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[1], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[2], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[2], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[3], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[4], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp ); c[p++] = ','; - p += sp_svg_number_write_de (c + p, transform[5], prec, min_exp); + p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp ); c[p++] = ')'; c[p] = '\000'; g_assert( p <= sizeof(c) ); diff --git a/src/svg/svg-color-test.h b/src/svg/svg-color-test.h index 49af63a5d..a540d4b1b 100644 --- a/src/svg/svg-color-test.h +++ b/src/svg/svg-color-test.h @@ -87,6 +87,8 @@ public: {1, true, "positive", "icc-color(positive, +0.1)"}, {1, true, "negative", "icc-color(negative, -0.1)"}, {0, false, "", "icc-color(named, value)"}, + {1, true, "hyphen-name", "icc-color(hyphen-name, 1)"}, + {1, true, "under_name", "icc-color(under_name, 1)"}, }; for ( size_t i = 0; i < G_N_ELEMENTS(cases); i++ ) { diff --git a/src/svg/svg-color.cpp b/src/svg/svg-color.cpp index 9040d6e43..a8e24c311 100644 --- a/src/svg/svg-color.cpp +++ b/src/svg/svg-color.cpp @@ -454,7 +454,11 @@ sp_svg_create_color_hash() return colors; } - +/* + * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj + * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z' + * Allowed ASCII remaining chars add: '-', '.', '0'-'9', + */ bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest ) { bool good = true; @@ -484,11 +488,14 @@ bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor } if ( !g_ascii_isalpha(*str) - && ( !(0x080 & *str) ) ) { + && ( !(0x080 & *str) ) + && (*str != '_') + && (*str != ':') ) { // Name must start with a certain type of character good = false; } else { - while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str) || (*str == '-') ) { + while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str) + || (*str == '-') || (*str == ':') || (*str == '_') || (*str == '.') ) { if ( dest ) { dest->colorProfile += *str; } diff --git a/src/svg/svg-length-test.h b/src/svg/svg-length-test.h index ce709b51d..833b4a08b 100644 --- a/src/svg/svg-length-test.h +++ b/src/svg/svg-length-test.h @@ -13,9 +13,13 @@ private: struct test_t { char const* str; SVGLength::Unit unit; float value; float computed; }; + struct testd_t { + char const* str; double val; int prec; int minexp; + }; static test_t const absolute_tests[12]; static test_t const relative_tests[3]; static char const * fail_tests[8]; + public: SvgLengthTest() { } @@ -28,68 +32,68 @@ public: void testRead() { for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) { - SVGLength l; - TSM_ASSERT(absolute_tests[i].str , l.read(absolute_tests[i].str)); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.unit , absolute_tests[i].unit); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.value , absolute_tests[i].value); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.computed , absolute_tests[i].computed); + SVGLength len; + TSM_ASSERT(absolute_tests[i].str , len.read(absolute_tests[i].str)); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed); } for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) { - SVGLength l; - TSM_ASSERT(relative_tests[i].str , l.read(relative_tests[i].str)); - l.update(7,13,19); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.unit , relative_tests[i].unit); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.value , relative_tests[i].value); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.computed , relative_tests[i].computed); + SVGLength len; + TSM_ASSERT(relative_tests[i].str , len.read(relative_tests[i].str)); + len.update(7,13,19); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.unit , relative_tests[i].unit); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.value , relative_tests[i].value); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.computed , relative_tests[i].computed); } for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) { - SVGLength l; - TSM_ASSERT(fail_tests[i] , !l.read(fail_tests[i])); + SVGLength len; + TSM_ASSERT(fail_tests[i] , !len.read(fail_tests[i])); } } void testReadOrUnset() { for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) { - SVGLength l; - l.readOrUnset(absolute_tests[i].str); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.unit , absolute_tests[i].unit); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.value , absolute_tests[i].value); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.computed , absolute_tests[i].computed); + SVGLength len; + len.readOrUnset(absolute_tests[i].str); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed); } for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) { - SVGLength l; - l.readOrUnset(relative_tests[i].str); - l.update(7,13,19); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.unit , relative_tests[i].unit); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.value , relative_tests[i].value); - TSM_ASSERT_EQUALS(relative_tests[i].str , l.computed , relative_tests[i].computed); + SVGLength len; + len.readOrUnset(relative_tests[i].str); + len.update(7,13,19); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.unit , relative_tests[i].unit); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.value , relative_tests[i].value); + TSM_ASSERT_EQUALS(relative_tests[i].str , len.computed , relative_tests[i].computed); } for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) { - SVGLength l; - l.readOrUnset(fail_tests[i], SVGLength::INCH, 123, 456); - TSM_ASSERT_EQUALS(fail_tests[i] , l.unit , SVGLength::INCH); - TSM_ASSERT_EQUALS(fail_tests[i] , l.value , 123); - TSM_ASSERT_EQUALS(fail_tests[i] , l.computed , 456); + SVGLength len; + len.readOrUnset(fail_tests[i], SVGLength::INCH, 123, 456); + TSM_ASSERT_EQUALS(fail_tests[i] , len.unit , SVGLength::INCH); + TSM_ASSERT_EQUALS(fail_tests[i] , len.value , 123); + TSM_ASSERT_EQUALS(fail_tests[i] , len.computed , 456); } } void testReadAbsolute() { for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) { - SVGLength l; - TSM_ASSERT(absolute_tests[i].str , l.readAbsolute(absolute_tests[i].str)); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.unit , absolute_tests[i].unit); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.value , absolute_tests[i].value); - TSM_ASSERT_EQUALS(absolute_tests[i].str , l.computed , absolute_tests[i].computed); + SVGLength len; + TSM_ASSERT(absolute_tests[i].str , len.readAbsolute(absolute_tests[i].str)); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value); + TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed); } for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) { - SVGLength l; - TSM_ASSERT(relative_tests[i].str , !l.readAbsolute(relative_tests[i].str)); + SVGLength len; + TSM_ASSERT(relative_tests[i].str , !len.readAbsolute(relative_tests[i].str)); } for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) { - SVGLength l; - TSM_ASSERT(fail_tests[i] , !l.readAbsolute(fail_tests[i])); + SVGLength len; + TSM_ASSERT(fail_tests[i] , !len.readAbsolute(fail_tests[i])); } } @@ -134,6 +138,23 @@ public: TSM_ASSERT_EQUALS(validStrings, validStrings.size(), 0u); } + void testPlaces() + { + testd_t const precTests[] = { + {"760", 761.92918978947023, 2, -8}, + {"761.9", 761.92918978947023, 4, -8}, + }; + + for ( size_t i = 0; i < G_N_ELEMENTS(precTests); i++ ) { + char buf[256] = {0}; + memset(buf, 0xCC, sizeof(buf)); // Make it easy to detect an overrun. + unsigned int retval = sp_svg_number_write_de( buf, sizeof(buf), precTests[i].val, precTests[i].prec, precTests[i].minexp ); + TSM_ASSERT_EQUALS("Number of chars written", retval, strlen(precTests[i].str)); + TSM_ASSERT_EQUALS("Numeric string written", std::string(buf), std::string(precTests[i].str)); + TSM_ASSERT_EQUALS(std::string("Buffer overrun ") + precTests[i].str, '\xCC', buf[retval + 1]); + } + } + // TODO: More tests }; diff --git a/src/svg/svg-length.cpp b/src/svg/svg-length.cpp index 1d41f9871..942f74d46 100644 --- a/src/svg/svg-length.cpp +++ b/src/svg/svg-length.cpp @@ -43,7 +43,7 @@ unsigned int sp_svg_number_read_f(gchar const *str, float *val) if ((gchar const *) e == str) { return 0; } - + *val = v; return 1; } @@ -59,7 +59,7 @@ unsigned int sp_svg_number_read_d(gchar const *str, double *val) if ((gchar const *) e == str) { return 0; } - + *val = v; return 1; } @@ -72,14 +72,14 @@ static unsigned int sp_svg_number_write_ui(gchar *buf, unsigned int val) c[16u - (++i)] = '0' + (val % 10u); val /= 10u; } while (val > 0u); - + memcpy(buf, &c[16u - i], i); buf[i] = 0; - + return i; } -static unsigned int sp_svg_number_write_i(gchar *buf, int val) +static unsigned int sp_svg_number_write_i(gchar *buf, int bufLen, int val) { int p = 0; unsigned int uval; @@ -91,11 +91,11 @@ static unsigned int sp_svg_number_write_i(gchar *buf, int val) } p += sp_svg_number_write_ui(buf+p, uval); - + return p; } -static unsigned sp_svg_number_write_d(gchar *buf, double val, unsigned int tprec, unsigned int fprec) +static unsigned sp_svg_number_write_d(gchar *buf, int bufLen, double val, unsigned int tprec, unsigned int fprec) { /* Process sign */ int i = 0; @@ -103,15 +103,15 @@ static unsigned sp_svg_number_write_d(gchar *buf, double val, unsigned int tprec buf[i++] = '-'; val = fabs(val); } - + /* Determine number of integral digits */ int idigits = 0; if (val >= 1.0) { idigits = (int) floor(log10(val)) + 1; } - + /* Determine the actual number of fractional digits */ - fprec = MAX(fprec, tprec - idigits); + fprec = MAX(static_cast<int>(fprec), static_cast<int>(tprec) - idigits); /* Round value */ val += 0.5 / pow(10.0, fprec); /* Extract integral and fractional parts */ @@ -146,7 +146,7 @@ static unsigned sp_svg_number_write_d(gchar *buf, double val, unsigned int tprec return end_i; } -unsigned int sp_svg_number_write_de(gchar *buf, double val, unsigned int tprec, int min_exp) +unsigned int sp_svg_number_write_de(gchar *buf, int bufLen, double val, unsigned int tprec, int min_exp) { int eval = (int)floor(log10(fabs(val))); if (val == 0.0 || eval < min_exp) { @@ -158,12 +158,12 @@ unsigned int sp_svg_number_write_de(gchar *buf, double val, unsigned int tprec, (unsigned int)eval+1; unsigned int maxnumdigitsWithExp = tprec + ( eval<0 ? 4 : 3 ); // It's not necessary to take larger exponents into account, because then maxnumdigitsWithoutExp is DEFINITELY larger if (maxnumdigitsWithoutExp <= maxnumdigitsWithExp) { - return sp_svg_number_write_d(buf, val, tprec, 0); + return sp_svg_number_write_d(buf, bufLen, val, tprec, 0); } else { val = eval < 0 ? val * pow(10.0, -eval) : val / pow(10.0, eval); - int p = sp_svg_number_write_d(buf, val, tprec, 0); + int p = sp_svg_number_write_d(buf, bufLen, val, tprec, 0); buf[p++] = 'e'; - p += sp_svg_number_write_i(buf + p, eval); + p += sp_svg_number_write_i(buf + p, bufLen - p, eval); return p; } } @@ -255,7 +255,7 @@ std::vector<SVGLength> sp_svg_length_list_read(gchar const *str) float computed; char *next = (char *) str; std::vector<SVGLength> list; - + while (sp_svg_length_read_lff(next, &unit, &value, &computed, &next)) { SVGLength length; @@ -266,9 +266,9 @@ std::vector<SVGLength> sp_svg_length_list_read(gchar const *str) (*next == ',' || *next == ' ' || *next == '\n' || *next == '\r' || *next == '\t')) { // the list can be comma- or space-separated, but we will be generous and accept // a mix, including newlines and tabs - next++; + next++; } - + if (!next || !*next) { break; } @@ -291,7 +291,7 @@ static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, if (e == str) { return 0; } - + if (!e[0]) { /* Unitless */ if (unit) { diff --git a/src/svg/svg-path-geom-test.h b/src/svg/svg-path-geom-test.h index 9a091cb86..1a084d5bf 100644 --- a/src/svg/svg-path-geom-test.h +++ b/src/svg/svg-path-geom-test.h @@ -159,7 +159,7 @@ public: } void testReadImplicitMoveto() { - TS_WARN("Currently lib2geom (/libnr) has no way of specifying the difference between '... z M 0,0 L 1,0' and '... z L 1,0', the SVG specification does state that these should be handled differently with respect to markers however, see the description of the 'orient' attribute of the 'marker' element."); + TS_WARN("Currently lib2geom (/libnr) has no way of specifying the difference between 'M 0,0 ... z M 0,0 L 1,0' and 'M 0,0 ... z L 1,0', the SVG specification does state that these should be handled differently with respect to markers however, see the description of the 'orient' attribute of the 'marker' element."); Geom::PathVector pv_good; pv_good.push_back(Geom::Path(Geom::Point(1,1))); pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2))); @@ -173,7 +173,7 @@ public: TSM_ASSERT(path_str, bpathEqual(pv,pv_good)); } { // Test relative version - char const * path_str = "M 1,1 L 2,2 z L 3,3 z"; + char const * path_str = "M 1,1 l 1,1 z l 2,2 z"; Geom::PathVector pv = sp_svg_read_pathv(path_str); TSM_ASSERT(path_str, bpathEqual(pv,pv_good)); } diff --git a/src/svg/svg.h b/src/svg/svg.h index 0eab21226..0b2c3ae53 100644 --- a/src/svg/svg.h +++ b/src/svg/svg.h @@ -1,5 +1,5 @@ -#ifndef __SP_SVG_H__ -#define __SP_SVG_H__ +#ifndef SEEN_SP_SVG_H +#define SEEN_SP_SVG_H /* * SVG data parser @@ -28,14 +28,14 @@ * - no valid end character checking * Return FALSE and let val untouched on error */ - -unsigned int sp_svg_number_read_f (const gchar *str, float *val); -unsigned int sp_svg_number_read_d (const gchar *str, double *val); + +unsigned int sp_svg_number_read_f( const gchar *str, float *val ); +unsigned int sp_svg_number_read_d( const gchar *str, double *val ); /* * No buffer overflow checking is done, so better wrap them if needed */ -unsigned int sp_svg_number_write_de (gchar *buf, double val, unsigned int tprec, int min_exp); +unsigned int sp_svg_number_write_de( gchar *buf, int bufLen, double val, unsigned int tprec, int min_exp ); /* Length */ @@ -48,9 +48,9 @@ unsigned int sp_svg_number_write_de (gchar *buf, double val, unsigned int tprec, * Any return value pointer can be NULL */ -unsigned int sp_svg_length_read_computed_absolute (const gchar *str, float *length); -std::vector<SVGLength> sp_svg_length_list_read (const gchar *str); -unsigned int sp_svg_length_read_ldd (const gchar *str, SVGLength::Unit *unit, double *value, double *computed); +unsigned int sp_svg_length_read_computed_absolute( const gchar *str, float *length ); +std::vector<SVGLength> sp_svg_length_list_read( const gchar *str ); +unsigned int sp_svg_length_read_ldd( const gchar *str, SVGLength::Unit *unit, double *value, double *computed ); std::string sp_svg_length_write_with_units(SVGLength const &length); @@ -59,15 +59,15 @@ bool sp_svg_transform_read(gchar const *str, Geom::Matrix *transform); gchar *sp_svg_transform_write(Geom::Matrix const &transform); gchar *sp_svg_transform_write(Geom::Matrix const *transform); -double sp_svg_read_percentage (const char * str, double def); +double sp_svg_read_percentage( const char * str, double def ); /* NB! As paths can be long, we use here dynamic string */ -Geom::PathVector sp_svg_read_pathv (char const * str); -gchar * sp_svg_write_path (Geom::PathVector const &p); -gchar * sp_svg_write_path (Geom::Path const &p); +Geom::PathVector sp_svg_read_pathv( char const * str ); +gchar * sp_svg_write_path( Geom::PathVector const &p ); +gchar * sp_svg_write_path( Geom::Path const &p ); -#endif +#endif // SEEN_SP_SVG_H /* Local Variables: diff --git a/src/text-context.cpp b/src/text-context.cpp index 538e13e43..e6f4f083b 100644 --- a/src/text-context.cpp +++ b/src/text-context.cpp @@ -352,7 +352,6 @@ sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEve sp_text_context_update_cursor(tc); sp_text_context_update_text_selection(tc); tc->dragging = 1; - sp_event_context_snap_window_open(event_context); } ret = TRUE; } @@ -369,7 +368,6 @@ sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEve sp_text_context_update_cursor(tc); sp_text_context_update_text_selection(tc); tc->dragging = 2; - sp_event_context_snap_window_open(event_context); ret = TRUE; } } @@ -381,14 +379,13 @@ sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEve sp_text_context_update_cursor(tc); sp_text_context_update_text_selection(tc); tc->dragging = 3; - sp_event_context_snap_window_open(event_context); ret = TRUE; } break; case GDK_BUTTON_RELEASE: if (event->button.button == 1 && tc->dragging && !event_context->space_panning) { tc->dragging = 0; - sp_event_context_snap_window_closed(event_context, false); //button release will also occur on a double-click; in that case suppress warnings + sp_event_context_discard_delayed_snap_event(event_context); ret = TRUE; } break; @@ -884,10 +881,10 @@ sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *cons if (MOD__CTRL_ONLY && tc->text) { SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end)); SPCSSAttr *css = sp_repr_css_attr_new(); - if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL) - sp_repr_css_set_property(css, "font-style", "italic"); - else + if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL) sp_repr_css_set_property(css, "font-style", "normal"); + else + sp_repr_css_set_property(css, "font-style", "italic"); sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css); sp_repr_css_attr_unref(css); sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, diff --git a/src/text-editing.cpp b/src/text-editing.cpp index 5b9db13d4..2bdee4c10 100644 --- a/src/text-editing.cpp +++ b/src/text-editing.cpp @@ -1547,8 +1547,9 @@ static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false; Inkscape::XML::Node *insert_after_repr; - if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item)); - else insert_after_repr = SP_OBJECT_REPR(*item); + if (!prepend) insert_after_repr = SP_OBJECT_REPR(*item); + else if (SP_OBJECT_PREV(*item)) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item)); + else insert_after_repr = NULL; while (SP_OBJECT_REPR(child)->childCount()) { Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild(); Inkscape::GC::anchor(move_repr); diff --git a/src/tweak-context.cpp b/src/tweak-context.cpp index 68e62b975..3f55d040b 100644 --- a/src/tweak-context.cpp +++ b/src/tweak-context.cpp @@ -208,23 +208,27 @@ sp_tweak_update_cursor (SPTweakContext *tc, bool with_shift) switch (tc->mode) { case TWEAK_MODE_MOVE: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>move</b>."), sel_message); - break; event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_MOVE_IN_OUT: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move in</b>; with Shift to <b>move out</b>."), sel_message); + event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_MOVE_JITTER: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move randomly</b>."), sel_message); + event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_SCALE: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>scale down</b>; with Shift to <b>scale up</b>."), sel_message); + event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_ROTATE: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>rotate clockwise</b>; with Shift, <b>counterclockwise</b>."), sel_message); + event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_MORELESS: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>duplicate</b>; with Shift, <b>delete</b>."), sel_message); + event_context->cursor_shape = cursor_tweak_move_xpm; break; case TWEAK_MODE_PUSH: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>push paths</b>."), sel_message); @@ -252,6 +256,7 @@ sp_tweak_update_cursor (SPTweakContext *tc, bool with_shift) break; case TWEAK_MODE_COLORPAINT: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>paint objects</b> with color."), sel_message); + event_context->cursor_shape = cursor_color_xpm; break; case TWEAK_MODE_COLORJITTER: tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>randomize colors</b>."), sel_message); diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index 7e41006be..af0ec0129 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -148,6 +148,8 @@ private: void _setClipboardColor(guint32); void _userWarn(SPDesktop *, char const *); + void _inkscape_wait_for_targets(std::list<Glib::ustring> &); + // private properites SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node @@ -1226,7 +1228,9 @@ Geom::Scale ClipboardManagerImpl::_getScale(Geom::Point const &min, Geom::Point */ Glib::ustring ClipboardManagerImpl::_getBestTarget() { - std::list<Glib::ustring> targets = _clipboard->wait_for_targets(); + // GTKmm's wait_for_targets() is broken, see the comment in _inkscape_wait_for_targets() + std::list<Glib::ustring> targets; // = _clipboard->wait_for_targets(); + _inkscape_wait_for_targets(targets); // clipboard target debugging snippet /* @@ -1365,6 +1369,35 @@ void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg) } +// GTKMM's clipboard::wait_for_targets is buggy and might return bogus, see +// +// https://bugs.launchpad.net/inkscape/+bug/296778 +// http://mail.gnome.org/archives/gtk-devel-list/2009-June/msg00062.html +// +// for details. Until this has been fixed upstream we will use our own implementation +// of this method, as copied from /gtkmm-2.16.0/gtk/gtkmm/clipboard.cc. +void ClipboardManagerImpl::_inkscape_wait_for_targets(std::list<Glib::ustring> &listTargets) +{ + //Get a newly-allocated array of atoms: + GdkAtom* targets = 0; + gint n_targets = 0; + gboolean test = gtk_clipboard_wait_for_targets( gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), &targets, &n_targets ); + if(!test) + n_targets = 0; //otherwise it will be -1. + + //Add the targets to the C++ container: + for(int i = 0; i < n_targets; i++) + { + //Convert the atom to a string: + gchar* const atom_name = gdk_atom_name(targets[i]); + + Glib::ustring target; + if(atom_name) + target = Glib::ScopedPtr<char>(atom_name).get(); //This frees the gchar*. + + listTargets.push_back(target); + } +} /* ####################################### ClipboardManager class diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert index 5cd437c26..565a24ecc 100644 --- a/src/ui/dialog/Makefile_insert +++ b/src/ui/dialog/Makefile_insert @@ -1,6 +1,6 @@ ## Makefile.am fragment sourced by src/Makefile.am. -if WITH_INKBOARD +##if WITH_INKBOARD ## inkboard_dialogs = \ ## ui/dialog/whiteboard-connect.cpp \ ## ui/dialog/whiteboard-connect.h \ @@ -8,7 +8,7 @@ if WITH_INKBOARD ## ui/dialog/whiteboard-sharewithchat.h \ ## ui/dialog/whiteboard-sharewithuser.cpp \ ## ui/dialog/whiteboard-sharewithuser.h -endif +##endif ink_common_sources += \ ui/dialog/aboutbox.cpp \ diff --git a/src/ui/dialog/calligraphic-presets.h b/src/ui/dialog/calligraphic-presets.h deleted file mode 100644 index 89f963ee8..000000000 --- a/src/ui/dialog/calligraphic-presets.h +++ /dev/null @@ -1,14 +0,0 @@ -char * stringlst = [ -/* TRANSLATORS: calligraphic pen type */ -N_("Dip pen"), -/* TRANSLATORS: calligraphic pen type */ -N_("Marker"), -/* TRANSLATORS: calligraphic pen type */ -N_("Brush"), -/* TRANSLATORS: calligraphic pen type */ -N_("Wiggly"), -/* TRANSLATORS: calligraphic pen type */ -N_("Splotchy"), -/* TRANSLATORS: calligraphic pen type */ -N_("Tracing"), -]; diff --git a/src/ui/dialog/document-properties.cpp b/src/ui/dialog/document-properties.cpp index bb9ab4d02..7e31b874a 100644 --- a/src/ui/dialog/document-properties.cpp +++ b/src/ui/dialog/document-properties.cpp @@ -381,8 +381,40 @@ DocumentProperties::populate_available_profiles(){ _menu.show_all(); } -void -DocumentProperties::linkSelectedProfile() +/** + * Cleans up name to remove disallowed characters. + * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj + * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z' + * Allowed ASCII remaining chars add: '-', '.', '0'-'9', + * + * @param str the string to clean up. + */ +static void sanitizeName( Glib::ustring& str ) +{ + if (str.size() > 1) { + char val = str.at(0); + if (((val < 'A') || (val > 'Z')) + && ((val < 'a') || (val > 'z')) + && (val != '_') + && (val != ':')) { + str.replace(0, 1, "-"); + } + for (Glib::ustring::size_type i = 1; i < str.size(); i++) { + char val = str.at(i); + if (((val < 'A') || (val > 'Z')) + && ((val < 'a') || (val > 'z')) + && ((val < '0') || (val > '9')) + && (val != '_') + && (val != ':') + && (val != '-') + && (val != '.')) { + str.replace(i, 1, "-"); + } + } + } +} + +void DocumentProperties::linkSelectedProfile() { //store this profile in the SVG document (create <color-profile> element in the XML) // TODO remove use of 'active' desktop @@ -396,7 +428,10 @@ DocumentProperties::linkSelectedProfile() } Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc()); Inkscape::XML::Node *cprofRepr = xml_doc->createElement("svg:color-profile"); - cprofRepr->setAttribute("name", (gchar*) _menu.get_active()->get_data("name")); + gchar* tmp = static_cast<gchar*>(_menu.get_active()->get_data("name")); + Glib::ustring nameStr = tmp ? tmp : "profile"; // TODO add some auto-numbering to avoid collisions + sanitizeName(nameStr); + cprofRepr->setAttribute("name", nameStr.c_str()); cprofRepr->setAttribute("xlink:href", (gchar*) _menu.get_active()->get_data("filepath")); // Checks whether there is a defs element. Creates it when needed @@ -653,7 +688,7 @@ void DocumentProperties::removeExternalScript(){ while ( current ) { SPObject* obj = SP_OBJECT(current->data); SPScript* script = (SPScript*) obj; - if (!name.compare(script->xlinkhref)){ + if (name == script->xlinkhref){ sp_repr_unparent(obj->repr); sp_document_done(SP_ACTIVE_DOCUMENT, SP_VERB_EDIT_REMOVE_EXTERNAL_SCRIPT, _("Remove external script")); } @@ -671,8 +706,12 @@ void DocumentProperties::populate_external_scripts_box(){ while ( current ) { SPObject* obj = SP_OBJECT(current->data); SPScript* script = (SPScript*) obj; - Gtk::TreeModel::Row row = *(_ExternalScriptsListStore->append()); - row[_ExternalScriptsListColumns.filenameColumn] = script->xlinkhref; + if (script->xlinkhref) + { + Gtk::TreeModel::Row row = *(_ExternalScriptsListStore->append()); + row[_ExternalScriptsListColumns.filenameColumn] = script->xlinkhref; + } + current = g_slist_next(current); } } diff --git a/src/ui/dialog/filedialog.cpp b/src/ui/dialog/filedialog.cpp index 172edf8a5..68c0926aa 100644 --- a/src/ui/dialog/filedialog.cpp +++ b/src/ui/dialog/filedialog.cpp @@ -105,16 +105,17 @@ Glib::ustring FileOpenDialog::getFilename() * Public factory method. Used in file.cpp */ FileSaveDialog *FileSaveDialog::create(Gtk::Window& parentWindow, - const Glib::ustring &path, + const Glib::ustring &path, FileDialogType fileTypes, const char *title, const Glib::ustring &default_key, - const gchar *docTitle) + const gchar *docTitle, + const Inkscape::Extension::FileSaveMethod save_method) { #ifdef WIN32 - FileSaveDialog *dialog = new FileSaveDialogImplWin32(parentWindow, path, fileTypes, title, default_key, docTitle); + FileSaveDialog *dialog = new FileSaveDialogImplWin32(parentWindow, path, fileTypes, title, default_key, docTitle, save_method); #else - FileSaveDialog *dialog = new FileSaveDialogImplGtk(parentWindow, path, fileTypes, title, default_key, docTitle); + FileSaveDialog *dialog = new FileSaveDialogImplGtk(parentWindow, path, fileTypes, title, default_key, docTitle, save_method); #endif return dialog; } @@ -165,11 +166,13 @@ void FileSaveDialog::appendExtension(Glib::ustring& path, Inkscape::Extension::O //# F I L E E X P O R T //######################################################################## +#ifdef NEW_EXPORT_DIALOG + /** * Public factory method. Used in file.cpp */ - FileExportDialog *FileExportDialog::create(Gtk::Window& parentWindow, - const Glib::ustring &path, +FileExportDialog *FileExportDialog::create(Gtk::Window& parentWindow, + const Glib::ustring &path, FileDialogType fileTypes, const char *title, const Glib::ustring &default_key) @@ -178,6 +181,8 @@ void FileSaveDialog::appendExtension(Glib::ustring& path, Inkscape::Extension::O return dialog; } +#endif // NEW_EXPORT_DIALOG + } //namespace Dialog } //namespace UI diff --git a/src/ui/dialog/filedialog.h b/src/ui/dialog/filedialog.h index 52dcd1b23..472c4ac0b 100644 --- a/src/ui/dialog/filedialog.h +++ b/src/ui/dialog/filedialog.h @@ -21,6 +21,10 @@ #include <set> #include <gtkmm.h> +#include "extension/system.h" + +class SPDocument; + namespace Inkscape { namespace Extension { class Extension; @@ -163,7 +167,8 @@ public: FileDialogType fileTypes, const char *title, const Glib::ustring &default_key, - const gchar *docTitle); + const gchar *docTitle, + const Inkscape::Extension::FileSaveMethod save_method); /** @@ -223,7 +228,9 @@ protected: }; //FileSaveDialog +//#define NEW_EXPORT_DIALOG +#ifdef NEW_EXPORT_DIALOG /** * This class provides an implementation-independent API for @@ -361,12 +368,13 @@ public: }; //FileExportDialog +#endif // NEW_EXPORT_DIALOG + } //namespace Dialog } //namespace UI } //namespace Inkscape - #endif /* __FILE_DIALOG_H__ */ /* diff --git a/src/ui/dialog/filedialogimpl-gtkmm.cpp b/src/ui/dialog/filedialogimpl-gtkmm.cpp index 70f2f2ae5..e650cf842 100644 --- a/src/ui/dialog/filedialogimpl-gtkmm.cpp +++ b/src/ui/dialog/filedialogimpl-gtkmm.cpp @@ -883,8 +883,11 @@ FileSaveDialogImplGtk::FileSaveDialogImplGtk( Gtk::Window &parentWindow, FileDialogType fileTypes, const Glib::ustring &title, const Glib::ustring &/*default_key*/, - const gchar* docTitle) : - FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_SAVE, fileTypes, "/dialogs/save_as") + const gchar* docTitle, + const Inkscape::Extension::FileSaveMethod save_method) : + FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_SAVE, fileTypes, + (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) ? "/dialogs/save_copy" : "/dialogs/save_as"), + save_method(save_method) { FileSaveDialog::myDocTitle = docTitle; @@ -922,7 +925,11 @@ FileSaveDialogImplGtk::FileSaveDialogImplGtk( Gtk::Window &parentWindow, //###### Do we want the .xxx extension automatically added? Inkscape::Preferences *prefs = Inkscape::Preferences::get(); fileTypeCheckbox.set_label(Glib::ustring(_("Append filename extension automatically"))); - fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_as/append_extension", true)); + if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) { + fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_copy/append_extension", true)); + } else { + fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_as/append_extension", true)); + } createFileTypeMenu(); fileTypeComboBox.set_size_request(200,40); @@ -1109,10 +1116,13 @@ FileSaveDialogImplGtk::show() Inkscape::Preferences *prefs = Inkscape::Preferences::get(); // Store changes of the "Append filename automatically" checkbox back to preferences. - prefs->setBool("/dialogs/save_as/append_extension", fileTypeCheckbox.get_active()); + if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) { + prefs->setBool("/dialogs/save_copy/append_extension", fileTypeCheckbox.get_active()); + } else { + prefs->setBool("/dialogs/save_as/append_extension", fileTypeCheckbox.get_active()); + } - // Store the last used save-as filetype to preferences. - prefs->setString("/dialogs/save_as/default", ( extension != NULL ? extension->get_id() : "" )); + Inkscape::Extension::store_file_extension_in_prefs ((extension != NULL ? extension->get_id() : "" ), save_method); cleanup( true ); @@ -1234,12 +1244,15 @@ void FileSaveDialogImplGtk::updateNameAndExtension() Inkscape::Extension::Output* newOut = extension ? dynamic_cast<Inkscape::Extension::Output*>(extension) : 0; if ( fileTypeCheckbox.get_active() && newOut ) { - // Append the file extension if it's not already present + // Append the file extension if it's not already present and display it in the file name entry field appendExtension(myFilename, newOut); + change_path(myFilename); } } +#ifdef NEW_EXPORT_DIALOG + //######################################################################## //# F I L E E X P O R T //######################################################################## @@ -1355,7 +1368,7 @@ FileExportDialogImpl::FileExportDialogImpl( Gtk::Window& parentWindow, destDPISpinner("DPI", _("Resolution (dots per inch)")) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - append_extension = prefs->getBool("/dialogs/save_as/append_extension", true); + append_extension = prefs->getBool("/dialogs/save_export/append_extension", true); /* One file at a time */ set_select_multiple(false); @@ -1565,8 +1578,8 @@ FileExportDialogImpl::show() append_extension = checkbox.get_active(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setBool("/dialogs/save_as/append_extension", append_extension); - prefs->setBool("/dialogs/save_as/default", ( extension != NULL ? extension->get_id() : "" )); + prefs->setBool("/dialogs/save_export/append_extension", append_extension); + prefs->setBool("/dialogs/save_export/default", ( extension != NULL ? extension->get_id() : "" )); */ return TRUE; } @@ -1596,6 +1609,8 @@ FileExportDialogImpl::getFilename() return myFilename; } +#endif // NEW_EXPORT_DIALOG + } //namespace Dialog } //namespace UI diff --git a/src/ui/dialog/filedialogimpl-gtkmm.h b/src/ui/dialog/filedialogimpl-gtkmm.h index 90dddce59..65bb38971 100644 --- a/src/ui/dialog/filedialogimpl-gtkmm.h +++ b/src/ui/dialog/filedialogimpl-gtkmm.h @@ -18,6 +18,7 @@ #define __FILE_DIALOGIMPL_H__ #include "filedialog.h" +#include "extension/system.h" //General includes #include <unistd.h> @@ -281,11 +282,12 @@ class FileSaveDialogImplGtk : public FileSaveDialog, public FileDialogBaseGtk public: FileSaveDialogImplGtk(Gtk::Window &parentWindow, - const Glib::ustring &dir, - FileDialogType fileTypes, - const Glib::ustring &title, - const Glib::ustring &default_key, - const gchar* docTitle); + const Glib::ustring &dir, + FileDialogType fileTypes, + const Glib::ustring &title, + const Glib::ustring &default_key, + const gchar* docTitle, + const Inkscape::Extension::FileSaveMethod save_method); virtual ~FileSaveDialogImplGtk(); @@ -302,6 +304,12 @@ private: void updateNameAndExtension(); /** + * The file save method (essentially whether the dialog was invoked by "Save as ..." or "Save a + * copy ..."), which is used to determine file extensions and save paths. + */ + Inkscape::Extension::FileSaveMethod save_method; + + /** * Fix to allow the user to type the file name */ Gtk::Entry *fileNameEntry; @@ -349,6 +357,8 @@ private: +#ifdef NEW_EXPORT_DIALOG + //######################################################################## //# F I L E E X P O R T //######################################################################## @@ -565,6 +575,7 @@ private: Glib::ustring myFilename; }; +#endif // NEW_EXPORT_DIALOG } // namespace Dialog } // namespace UI diff --git a/src/ui/dialog/filedialogimpl-win32.cpp b/src/ui/dialog/filedialogimpl-win32.cpp index 6fd753abb..d22a368f2 100644 --- a/src/ui/dialog/filedialogimpl-win32.cpp +++ b/src/ui/dialog/filedialogimpl-win32.cpp @@ -193,11 +193,11 @@ void FileOpenDialogImplWin32::createFilterMenu() ustring all_inkscape_files_filter, all_image_files_filter, all_vectors_filter, all_bitmaps_filter; Filter all_files, all_inkscape_files, all_image_files, all_vectors, all_bitmaps; - const gchar *all_files_filter_name = N_("All Files"); - const gchar *all_inkscape_files_filter_name = N_("All Inkscape Files"); - const gchar *all_image_files_filter_name = N_("All Images"); - const gchar *all_vectors_filter_name = N_("All Vectors"); - const gchar *all_bitmaps_filter_name = N_("All Bitmaps"); + const gchar *all_files_filter_name = _("All Files"); + const gchar *all_inkscape_files_filter_name = _("All Inkscape Files"); + const gchar *all_image_files_filter_name = _("All Images"); + const gchar *all_vectors_filter_name = _("All Vectors"); + const gchar *all_bitmaps_filter_name = _("All Bitmaps"); // Calculate the amount of memory required int filter_count = 5; // 5 - one for each filter type @@ -608,9 +608,12 @@ LRESULT CALLBACK FileOpenDialogImplWin32::preview_wnd_proc(HWND hwnd, UINT uMsg, if(pImpl->_path_string[0] == 0) { + WCHAR* noFileText=(WCHAR*)g_utf8_to_utf16(_("No file selected"), + -1, NULL, NULL, NULL); FillRect(dc, &rcClient, (HBRUSH)(COLOR_3DFACE + 1)); - DrawText(dc, _("No file selected"), -1, &rcClient, + DrawTextW(dc, noFileText, -1, &rcClient, DT_CENTER | DT_VCENTER | DT_NOPREFIX); + g_free(noFileText); } else if(pImpl->_preview_bitmap != NULL) { @@ -1494,8 +1497,10 @@ FileSaveDialogImplWin32::FileSaveDialogImplWin32(Gtk::Window &parent, FileDialogType fileTypes, const char *title, const Glib::ustring &/*default_key*/, - const char *docTitle) : - FileDialogBaseWin32(parent, dir, title, fileTypes, "dialogs.save_as"), + const char *docTitle, + const Inkscape::Extension::FileSaveMethod save_method) : + FileDialogBaseWin32(parent, dir, title, fileTypes, + (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) ? "dialogs.save_copy" : "dialogs.save_as"), _title_label(NULL), _title_edit(NULL) { diff --git a/src/ui/dialog/filedialogimpl-win32.h b/src/ui/dialog/filedialogimpl-win32.h index bb026169a..4234c1782 100644 --- a/src/ui/dialog/filedialogimpl-win32.h +++ b/src/ui/dialog/filedialogimpl-win32.h @@ -120,9 +120,9 @@ public: /// @param title The title caption for the dialog in UTF-8 /// @param type The dialog type FileOpenDialogImplWin32(Gtk::Window &parent, - const Glib::ustring &dir, - FileDialogType fileTypes, - const char *title); + const Glib::ustring &dir, + FileDialogType fileTypes, + const char *title); /// Destructor virtual ~FileOpenDialogImplWin32(); @@ -306,11 +306,12 @@ class FileSaveDialogImplWin32 : public FileSaveDialog, public FileDialogBaseWin3 public: FileSaveDialogImplWin32(Gtk::Window &parent, - const Glib::ustring &dir, - FileDialogType fileTypes, - const char *title, - const Glib::ustring &default_key, - const char *docTitle); + const Glib::ustring &dir, + FileDialogType fileTypes, + const char *title, + const Glib::ustring &default_key, + const char *docTitle, + const Inkscape::Extension::FileSaveMethod save_method); /// Destructor virtual ~FileSaveDialogImplWin32(); diff --git a/src/ui/dialog/filter-effects-dialog.cpp b/src/ui/dialog/filter-effects-dialog.cpp index 1a73dd9a1..c7f505046 100644 --- a/src/ui/dialog/filter-effects-dialog.cpp +++ b/src/ui/dialog/filter-effects-dialog.cpp @@ -15,7 +15,7 @@ # include <config.h> #endif -#include <gtk/gtktreeview.h> +#include <gtk/gtk.h> #include <gtkmm/cellrenderertext.h> #include <gtkmm/colorbutton.h> #include <gtkmm/messagedialog.h> @@ -462,7 +462,7 @@ public: ColorMatrixValues() : AttrWidget(SP_ATTR_VALUES), // TRANSLATORS: this dialog is accessible via menu Filters - Filter editor - _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on colour space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")), + _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")), _saturation(0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES), _angle(0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES), _label(_("None"), Gtk::ALIGN_LEFT), @@ -1315,7 +1315,7 @@ void FilterEffectsDialog::FilterModifier::add_filter() const int count = _model->children().size(); std::ostringstream os; - os << "filter" << count; + os << _("filter") << count; filter->setLabel(os.str().c_str()); update_filters(); @@ -2192,7 +2192,7 @@ void FilterEffectsDialog::init_settings_widgets() //TRANSLATORS: for info on "Kernel", see http://en.wikipedia.org/wiki/Kernel_(matrix) _convolve_matrix = _settings->add_matrix(SP_ATTR_KERNELMATRIX, _("Kernel:"), _("This matrix describes the convolve operation that is applied to the input image in order to calculate the pixel colors at the output. Different arrangements of values in this matrix result in various possible visual effects. An identity matrix would lead to a motion blur effect (parallel to the matrix diagonal) while a matrix filled with a constant non-zero value would lead to a common blur effect.")); _convolve_order->signal_attr_changed().connect(sigc::mem_fun(*this, &FilterEffectsDialog::convolve_order_changed)); - _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 20, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result.")); + _settings->add_spinslider(0, SP_ATTR_DIVISOR, _("Divisor:"), 0, 1000, 1, 0.1, 2, _("After applying the kernelMatrix to the input image to yield a number, that number is divided by divisor to yield the final destination color value. A divisor that is the sum of all the matrix values tends to have an evening effect on the overall color intensity of the result.")); _settings->add_spinslider(0, SP_ATTR_BIAS, _("Bias:"), -10, 10, 1, 0.01, 1, _("This value is added to each component. This is useful to define a constant value as the zero response of the filter.")); _settings->add_combo(CONVOLVEMATRIX_EDGEMODE_DUPLICATE, SP_ATTR_EDGEMODE, _("Edge Mode:"), ConvolveMatrixEdgeModeConverter, _("Determines how to extend the input image as necessary with color values so that the matrix operations can be applied when the kernel is positioned at or near the edge of the input image.")); _settings->add_checkbutton(false, SP_ATTR_PRESERVEALPHA, _("Preserve Alpha"), "true", "false", _("If set, the alpha channel won't be altered by this filter primitive.")); @@ -2279,7 +2279,7 @@ void FilterEffectsDialog::update_primitive_infobox() break; case(NR_FILTER_COLORMATRIX): _infobox_icon.set_from_icon_name("feColorMatrix-icon", Gtk::ICON_SIZE_DIALOG); - _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to colour of each rendered pixel. This allows for effects like turning object to grayscale, modifying colour saturation and changing colour hue.")); + _infobox_desc.set_markup(_("The <b>feColorMatrix</b> filter primitive applies a matrix transformation to color of each rendered pixel. This allows for effects like turning object to grayscale, modifying color saturation and changing color hue.")); break; case(NR_FILTER_COMPONENTTRANSFER): _infobox_icon.set_from_icon_name("feComponentTransfer-icon", Gtk::ICON_SIZE_DIALOG); @@ -2319,7 +2319,7 @@ void FilterEffectsDialog::update_primitive_infobox() break; case(NR_FILTER_MORPHOLOGY): _infobox_icon.set_from_icon_name("feMorphology-icon", Gtk::ICON_SIZE_DIALOG); - _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-colour objects erode makes the object thinner and dilate makes it thicker.")); + _infobox_desc.set_markup(_("The <b>feMorphology</b> filter primitive provides erode and dilate effects. For single-color objects erode makes the object thinner and dilate makes it thicker.")); break; case(NR_FILTER_OFFSET): _infobox_icon.set_from_icon_name("feOffset-icon", Gtk::ICON_SIZE_DIALOG); diff --git a/src/ui/dialog/guides.cpp b/src/ui/dialog/guides.cpp index c5d2ae488..3a7964ba2 100644 --- a/src/ui/dialog/guides.cpp +++ b/src/ui/dialog/guides.cpp @@ -241,7 +241,7 @@ void GuidelinePropertiesDialog::_setup() { g_free(label); } { - gchar *guide_description = sp_guide_description(_guide); + gchar *guide_description = sp_guide_description(_guide, false); gchar *label = g_strdup_printf(_("Current: %s"), guide_description); g_free(guide_description); _label_descr.set_markup(label); diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index a81446a03..c7dc789ca 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -120,7 +120,7 @@ InkscapePreferences::InkscapePreferences() initPageCMS(); initPageGrids(); initPageSVGOutput(); - initPageAutosave(); + initPageSave(); initPageImportExport(); initPageMouse(); initPageScrolling(); @@ -220,16 +220,20 @@ void InkscapePreferences::initPageSnapping() _snap_delay.init("/options/snapdelay/value", 0, 1000, 50, 100, 300, 0); _page_snapping.add_line( false, _("Delay (in ms):"), _snap_delay, "", - _("Postpone snapping as long as the mouse is moving, and then wait an additional fraction of a second. This additional delay is specified here. When set to zero or to a very small number, snapping will be immediate"), true); + _("Postpone snapping as long as the mouse is moving, and then wait an additional fraction of a second. This additional delay is specified here. When set to zero or to a very small number, snapping will be immediate."), true); _snap_closest_only.init( _("Only snap the node closest to the pointer"), "/options/snapclosestonly/value", false); _page_snapping.add_line( false, "", _snap_closest_only, "", - _("Only try to snap the node that is initialy closest to the mouse pointer")); + _("Only try to snap the node that is initially closest to the mouse pointer")); _snap_weight.init("/options/snapweight/value", 0, 1, 0.1, 0.2, 0.5, 1); _page_snapping.add_line( false, _("Weight factor:"), _snap_weight, "", _("When multiple snap solutions are found, then Inkscape can either prefer the closest transformation (when set to 0), or prefer the node that was initially the closest to the pointer (when set to 1)"), true); + _snap_mouse_pointer.init( _("Snap the mouse pointer when dragging a constrained knot"), "/options/snapmousepointer/value", false); + _page_snapping.add_line( false, "", _snap_mouse_pointer, "", + _("When dragging a knot along a constraint line, then snap the position of the mouse pointer instead of snapping the projection of the knot onto the constraint line")); + this->AddPage(_page_snapping, _("Snapping"), PREFS_PAGE_SNAPPING); } @@ -321,7 +325,7 @@ void StyleFromSelectionToTool(Glib::ustring const &prefs_path, StyleSwatch *swat if (!css) return; // only store text style for the text tool - if (prefs_path == "/tools/text") { + if (prefs_path != "/tools/text") { css = sp_css_attr_unset_text (css); } @@ -1105,19 +1109,24 @@ void InkscapePreferences::initPageUI() } -void InkscapePreferences::initPageAutosave() +void InkscapePreferences::initPageSave() { + _save_use_current_dir.init( _("Use current directory for \"Save As ...\""), "/dialogs/save_as/use_current_dir", true); + _page_save.add_line( false, "", _save_use_current_dir, "", + _("When this option is on, the \"Save as...\" dialog will always open in the directory where the currently open document is. When it's off, it will open in the directory where you last saved a file using that dialog."), true); + + // Autosave options - _autosave_autosave_enable.init( _("Enable autosave (requires restart)"), "/options/autosave/enable", false); - _page_autosave.add_line(false, "", _autosave_autosave_enable, "", _("Automatically save the current document(s) at a given interval, thus minimizing loss in case of a crash"), false); - _autosave_autosave_interval.init("/options/autosave/interval", 1.0, 10800.0, 1.0, 10.0, 10.0, true, false); - _page_autosave.add_line(true, _("Interval (in minutes):"), _autosave_autosave_interval, "", _("Interval (in minutes) at which document will be autosaved"), false); - _autosave_autosave_path.init("/options/autosave/path", true); + _save_autosave_enable.init( _("Enable autosave (requires restart)"), "/options/autosave/enable", false); + _page_save.add_line(false, "", _save_autosave_enable, "", _("Automatically save the current document(s) at a given interval, thus minimizing loss in case of a crash"), false); + _save_autosave_interval.init("/options/autosave/interval", 1.0, 10800.0, 1.0, 10.0, 10.0, true, false); + _page_save.add_line(true, _("Interval (in minutes):"), _save_autosave_interval, "", _("Interval (in minutes) at which document will be autosaved"), false); + _save_autosave_path.init("/options/autosave/path", true); //TRANSLATORS: only translate "string" in "context|string". // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS - _page_autosave.add_line(true, Q_("filesystem|Path:"), _autosave_autosave_path, "", _("The directory where autosaves will be written"), false); - _autosave_autosave_max.init("/options/autosave/max", 1.0, 100.0, 1.0, 10.0, 10.0, true, false); - _page_autosave.add_line(true, _("Maximum number of autosaves:"), _autosave_autosave_max, "", _("Maximum number of autosaved files; use this to limit the storage space used"), false); + _page_save.add_line(true, Q_("filesystem|Path:"), _save_autosave_path, "", _("The directory where autosaves will be written"), false); + _save_autosave_max.init("/options/autosave/max", 1.0, 100.0, 1.0, 10.0, 10.0, true, false); + _page_save.add_line(true, _("Maximum number of autosaves:"), _save_autosave_max, "", _("Maximum number of autosaved files; use this to limit the storage space used"), false); /* When changing the interval or enabling/disabling the autosave function, * update our running configuration @@ -1133,7 +1142,7 @@ void InkscapePreferences::initPageAutosave() // ----------- - this->AddPage(_page_autosave, _("Autosave"), PREFS_PAGE_AUTOSAVE); + this->AddPage(_page_save, _("Save"), PREFS_PAGE_SAVE); } void InkscapePreferences::initPageBitmaps() @@ -1271,7 +1280,7 @@ void InkscapePreferences::initPageMisc() _misc_simpl.init("/options/simplifythreshold/value", 0.0001, 1.0, 0.0001, 0.0010, 0.0010, false, false); _page_misc.add_line( false, _("Simplification threshold:"), _misc_simpl, "", - _("How strong is the Simplify command by default. If you invoke this command several times in quick succession, it will act more and more aggressively; invoking it again after a pause restores the default threshold."), false); + _("How strong is the Node tool's Simplify command by default. If you invoke this command several times in quick succession, it will act more and more aggressively; invoking it again after a pause restores the default threshold."), false); _misc_latency_skew.init("/debug/latency/skew", 0.5, 2.0, 0.01, 0.10, 1.0, false, false); _page_misc.add_line( false, _("Latency skew:"), _misc_latency_skew, _("(requires restart)"), diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 7bcbe7d49..364b0eb1d 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -69,7 +69,7 @@ enum { PREFS_PAGE_CMS, PREFS_PAGE_GRIDS, PREFS_PAGE_SVGOUTPUT, - PREFS_PAGE_AUTOSAVE, + PREFS_PAGE_SAVE, PREFS_PAGE_IMPORTEXPORT, PREFS_PAGE_MOUSE, PREFS_PAGE_SCROLLING, @@ -117,7 +117,7 @@ protected: DialogPage _page_mouse, _page_scrolling, _page_snapping, _page_steps, _page_tools, _page_windows, _page_clones, _page_mask, _page_transforms, _page_filters, _page_select, _page_importexport, _page_cms, _page_grids, _page_svgoutput, _page_misc, - _page_ui, _page_autosave, _page_bitmaps, _page_spellcheck; + _page_ui, _page_save, _page_bitmaps, _page_spellcheck; DialogPage _page_selector, _page_node, _page_tweak, _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; @@ -131,7 +131,7 @@ protected: PrefCheckButton _wheel_zoom; Gtk::HScale *_slider_snapping_delay; - PrefCheckButton _snap_indicator, _snap_closest_only; + PrefCheckButton _snap_indicator, _snap_closest_only, _snap_mouse_pointer; PrefCombo _steps_rot_snap; PrefCheckButton _steps_compass; @@ -209,10 +209,11 @@ protected: PrefCheckButton _misc_bitmap_autoreload; PrefSpinButton _bitmap_copy_res; - PrefCheckButton _autosave_autosave_enable; - PrefSpinButton _autosave_autosave_interval; - PrefEntry _autosave_autosave_path; - PrefSpinButton _autosave_autosave_max; + PrefCheckButton _save_use_current_dir; + PrefCheckButton _save_autosave_enable; + PrefSpinButton _save_autosave_interval; + PrefEntry _save_autosave_path; + PrefSpinButton _save_autosave_max; Gtk::ComboBoxText _cms_display_profile; PrefCheckButton _cms_from_display; @@ -298,7 +299,7 @@ protected: void initPageSVGOutput(); void initPageUI(); void initPageSpellcheck(); - void initPageAutosave(); + void initPageSave(); void initPageBitmaps(); void initPageMisc(); void initPageI18n(); diff --git a/src/ui/dialog/layers.cpp b/src/ui/dialog/layers.cpp index d79f18801..a06b6c9b6 100644 --- a/src/ui/dialog/layers.cpp +++ b/src/ui/dialog/layers.cpp @@ -73,12 +73,12 @@ public: SPObject* _target; }; -static gboolean layers_panel_activated( GtkObject */*object*/, GdkEvent * /*event*/, gpointer data ) +static gboolean layers_panel_activated(Inkscape::Application * /*inkscape*/, SPDesktop *desktop, gpointer data ) { if ( data ) { LayersPanel* panel = reinterpret_cast<LayersPanel*>(data); - panel->setDesktop(panel->getDesktop()); + panel->setDesktop(desktop); } return FALSE; @@ -95,7 +95,6 @@ static gboolean layers_panel_deactivated( GtkObject */*object*/, GdkEvent * /*ev return FALSE; } - void LayersPanel::_styleButton( Gtk::Button& btn, SPDesktop *desktop, unsigned int code, char const* iconName, char const* fallback ) { bool set = false; @@ -349,20 +348,22 @@ bool LayersPanel::_checkForSelected(const Gtk::TreePath &path, const Gtk::TreeIt void LayersPanel::_layersChanged() { // g_message("_layersChanged()"); - SPDocument* document = _desktop->doc(); - SPObject* root = document->root; - if ( root ) { - _selectedConnection.block(); - if ( _mgr && _mgr->includes( root ) ) { - SPObject* target = _desktop->currentLayer(); - _store->clear(); - -#if DUMP_LAYERS - g_message("root:%p {%s} [%s]", root, root->id, root->label() ); -#endif // DUMP_LAYERS - _addLayer( document, root, 0, target, 0 ); + if(_desktop) { + SPDocument* document = _desktop->doc(); + SPObject* root = document->root; + if ( root ) { + _selectedConnection.block(); + if ( _mgr && _mgr->includes( root ) ) { + SPObject* target = _desktop->currentLayer(); + _store->clear(); + + #if DUMP_LAYERS + g_message("root:%p {%s} [%s]", root, root->id, root->label() ); + #endif // DUMP_LAYERS + _addLayer( document, root, 0, target, 0 ); + } + _selectedConnection.unblock(); } - _selectedConnection.unblock(); } } @@ -414,9 +415,9 @@ SPObject* LayersPanel::_selectedLayer() void LayersPanel::_pushTreeSelectionToCurrent() { - SPObject* inTree = _selectedLayer(); // TODO hunt down the possible API abuse in getting NULL - if ( _desktop->currentRoot() ) { + if ( _desktop && _desktop->currentRoot() ) { + SPObject* inTree = _selectedLayer(); if ( inTree ) { SPObject* curr = _desktop->currentLayer(); if ( curr != inTree ) { @@ -459,6 +460,7 @@ void LayersPanel::_checkTreeSelection() void LayersPanel::_preToggle( GdkEvent const *event ) { + if ( _toggleEvent ) { gdk_event_free(_toggleEvent); _toggleEvent = 0; @@ -472,6 +474,8 @@ void LayersPanel::_preToggle( GdkEvent const *event ) void LayersPanel::_toggled( Glib::ustring const& str, int targetCol ) { + g_return_if_fail(_desktop != NULL); + Gtk::TreeModel::Children::iterator iter = _tree.get_model()->get_iter(str); Gtk::TreeModel::Row row = *iter; @@ -642,9 +646,7 @@ LayersPanel::LayersPanel() : _layersPage.pack_end(_compositeSettings, Gtk::PACK_SHRINK); _layersPage.pack_end(_buttonsRow, Gtk::PACK_SHRINK); - _notebook.append_page(_layersPage, _("Layers")); - - _getContents()->pack_start(_notebook, Gtk::PACK_EXPAND_WIDGET); + _getContents()->pack_start(_layersPage, Gtk::PACK_EXPAND_WIDGET); SPDesktop* targetDesktop = getDesktop(); @@ -656,7 +658,9 @@ LayersPanel::LayersPanel() : _buttonsRow.add( *btn ); btn = manage( new Gtk::Button() ); - _styleButton( *btn, targetDesktop, SP_VERB_LAYER_TO_TOP, GTK_STOCK_GOTO_TOP, _("Top") ); + //TRANSLATORS: only translate "string" in "context|string". + // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS + _styleButton( *btn, targetDesktop, SP_VERB_LAYER_TO_TOP, GTK_STOCK_GOTO_TOP, Q_("layers|Top") ); btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &LayersPanel::_takeAction), (int)BUTTON_TOP) ); _watchingNonTop.push_back( btn ); _buttonsRow.add( *btn ); @@ -722,6 +726,7 @@ LayersPanel::LayersPanel() : g_signal_connect( G_OBJECT(INKSCAPE), "activate_desktop", G_CALLBACK( layers_panel_activated ), this ); g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", G_CALLBACK( layers_panel_deactivated ), this ); + setDesktop( targetDesktop ); show_all_children(); @@ -763,7 +768,7 @@ void LayersPanel::setDesktop( SPDesktop* desktop ) _desktop = 0; } - _desktop = getDesktop(); + _desktop = Panel::getDesktop(); if ( _desktop ) { //setLabel( _desktop->doc()->name ); diff --git a/src/ui/dialog/layers.h b/src/ui/dialog/layers.h index 1f593b9c6..16b3be350 100644 --- a/src/ui/dialog/layers.h +++ b/src/ui/dialog/layers.h @@ -117,7 +117,6 @@ private: Gtk::ScrolledWindow _scroller; Gtk::Menu _popupMenu; Gtk::SpinButton _spinBtn; - Gtk::Notebook _notebook; Gtk::VBox _layersPage; UI::Widget::StyleSubject::CurrentLayer _subject; diff --git a/src/ui/dialog/livepatheffect-editor.cpp b/src/ui/dialog/livepatheffect-editor.cpp index dd2dc8250..dec437be9 100644 --- a/src/ui/dialog/livepatheffect-editor.cpp +++ b/src/ui/dialog/livepatheffect-editor.cpp @@ -305,7 +305,7 @@ LivePathEffectEditor::effect_list_reload(SPLPEItem *lpeitem) row[columns.col_visible] = (*it)->lpeobject->get_lpe()->isVisible(); } else { Gtk::TreeModel::Row row = *(effectlist_store->append()); - row[columns.col_name] = "Unknown effect!"; + row[columns.col_name] = _("Unknown effect"); row[columns.lperef] = *it; row[columns.col_visible] = false; } diff --git a/src/ui/dialog/ocaldialogs.cpp b/src/ui/dialog/ocaldialogs.cpp index ba572336c..d3887b1ca 100644 --- a/src/ui/dialog/ocaldialogs.cpp +++ b/src/ui/dialog/ocaldialogs.cpp @@ -41,6 +41,7 @@ namespace Dialog /** * Callback for fileNameEntry widget */ +/* void FileExportToOCALDialog::fileNameEntryChangedCallback() { if (!fileNameEntry) @@ -53,25 +54,32 @@ void FileExportToOCALDialog::fileNameEntryChangedCallback() myFilename = fileName; response(Gtk::RESPONSE_OK); } - +*/ /** * Constructor */ +/* FileExportToOCALDialog::FileExportToOCALDialog(Gtk::Window &parentWindow, FileDialogType fileTypes, const Glib::ustring &title) : FileDialogOCALBase(title, parentWindow) { - /* +*/ + /* * Start Taking the vertical Box and putting a Label * and a Entry to take the filename * Later put the extension selection and checkbox (?) */ /* Initalize to Autodetect */ +/* extension = NULL; +*/ /* No filename to start out with */ +/* myFilename = ""; +*/ /* Set our dialog type (save, export, etc...)*/ +/* dialogType = fileTypes; Gtk::VBox *vbox = get_vbox(); @@ -102,17 +110,19 @@ FileExportToOCALDialog::FileExportToOCALDialog(Gtk::Window &parentWindow, show_all_children(); } - +*/ /** * Destructor */ +/* FileExportToOCALDialog::~FileExportToOCALDialog() { } - +*/ /** * Show this dialog modally. Return true if user hits [OK] */ +/* bool FileExportToOCALDialog::show() { @@ -130,10 +140,11 @@ FileExportToOCALDialog::show() return FALSE; } } - +*/ /** * Get the file name chosen by the user. Valid after an [OK] */ +/* Glib::ustring FileExportToOCALDialog::getFilename() { @@ -150,7 +161,7 @@ FileExportToOCALDialog::change_title(const Glib::ustring& title) { this->set_title(title); } - +*/ //######################################################################## //# F I L E E X P O R T T O O C A L P A S S W O R D @@ -160,14 +171,17 @@ FileExportToOCALDialog::change_title(const Glib::ustring& title) /** * Constructor */ +/* FileExportToOCALPasswordDialog::FileExportToOCALPasswordDialog(Gtk::Window &parentWindow, const Glib::ustring &title) : FileDialogOCALBase(title, parentWindow) { +*/ /* * Start Taking the vertical Box and putting 2 Labels * and 2 Entries to take the username and password */ /* No username and password to start out with */ +/* myUsername = ""; myPassword = ""; @@ -200,18 +214,20 @@ FileExportToOCALPasswordDialog::FileExportToOCALPasswordDialog(Gtk::Window &pare show_all_children(); } - +*/ /** * Destructor */ +/* FileExportToOCALPasswordDialog::~FileExportToOCALPasswordDialog() { } - +*/ /** * Show this dialog modally. Return true if user hits [OK] */ +/* bool FileExportToOCALPasswordDialog::show() { @@ -229,20 +245,22 @@ FileExportToOCALPasswordDialog::show() return FALSE; } } - +*/ /** * Get the username. Valid after an [OK] */ +/* Glib::ustring FileExportToOCALPasswordDialog::getUsername() { myUsername = usernameEntry->get_text(); return myUsername; } - +*/ /** * Get the password. Valid after an [OK] */ +/* Glib::ustring FileExportToOCALPasswordDialog::getPassword() { @@ -255,7 +273,7 @@ FileExportToOCALPasswordDialog::change_title(const Glib::ustring& title) { this->set_title(title); } - +*/ //######################################################################### //### F I L E I M P O R T F R O M O C A L diff --git a/src/ui/dialog/ocaldialogs.h b/src/ui/dialog/ocaldialogs.h index 75d57fc59..ce26f2148 100644 --- a/src/ui/dialog/ocaldialogs.h +++ b/src/ui/dialog/ocaldialogs.h @@ -105,75 +105,86 @@ protected: /** * Our implementation of the FileExportToOCALDialog interface. */ +/* class FileExportToOCALDialog : public FileDialogOCALBase { public: +*/ /** * Constructor * @param fileTypes one of FileDialogTypes * @param title the title of the dialog * @param key a list of file types from which the user can select */ +/* FileExportToOCALDialog(Gtk::Window& parentWindow, FileDialogType fileTypes, - const Glib::ustring &title); - + const Glib::ustring &title); +*/ /** * Destructor. * Perform any necessary cleanups. */ +/* ~FileExportToOCALDialog(); - +*/ /** * Show an SaveAs file selector. * @return the selected path if user selected one, else NULL */ +/* bool show(); Glib::ustring getFilename(); Glib::ustring myFilename; - +*/ /** * Change the window title. */ +/* void change_title(const Glib::ustring& title); private: - +*/ /** * Fix to allow the user to type the file name */ +/* Gtk::Entry *fileNameEntry; - +*/ /** * Data mirror of the combo box */ +/* std::vector<FileType> fileTypes; // Child widgets Gtk::HBox childBox; Gtk::VBox checksBox; Gtk::HBox fileBox; - +*/ /** * The extension to use to write this file */ +/* Inkscape::Extension::Extension *extension; - +*/ /** * Callback for user input into fileNameEntry */ +/* void fileNameEntryChangedCallback(); - +*/ /** * List of known file extensions. */ +/* std::set<Glib::ustring> knownExtensions; }; //FileExportToOCAL - +*/ //######################################################################## //# F I L E E X P O R T T O O C A L P A S S W O R D @@ -183,45 +194,52 @@ private: /** * Our implementation of the FileExportToOCALPasswordDialog interface. */ +/* class FileExportToOCALPasswordDialog : public FileDialogOCALBase { public: +*/ /** * Constructor * @param title the title of the dialog */ +/* FileExportToOCALPasswordDialog(Gtk::Window& parentWindow, const Glib::ustring &title); - +*/ /** * Destructor. * Perform any necessary cleanups. */ +/* ~FileExportToOCALPasswordDialog(); - +*/ /** * Show 2 entry to input username and password. */ +/* bool show(); Glib::ustring getUsername(); Glib::ustring getPassword(); - +*/ /** * Change the window title. */ +/* void change_title(const Glib::ustring& title); Glib::ustring myUsername; Glib::ustring myPassword; private: - +*/ /** * Fix to allow the user to type the file name */ +/* Gtk::Entry *usernameEntry; Gtk::Entry *passwordEntry; @@ -231,7 +249,7 @@ private: Gtk::HBox passBox; }; //FileExportToOCALPassword - +*/ //######################################################################### diff --git a/src/ui/dialog/print.cpp b/src/ui/dialog/print.cpp index d15773ecb..f9db265d6 100644 --- a/src/ui/dialog/print.cpp +++ b/src/ui/dialog/print.cpp @@ -13,6 +13,7 @@ #endif #ifdef WIN32 #include <io.h> +#include <windows.h> #endif #include <gtkmm/stock.h> @@ -31,7 +32,7 @@ static void -draw_page (GtkPrintOperation */*operation*/, +draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint /*page_nr*/, gpointer user_data) @@ -106,6 +107,24 @@ draw_page (GtkPrintOperation */*operation*/, cairo_surface_t *surface = cairo_get_target(cr); cairo_matrix_t ctm; cairo_get_matrix(cr, &ctm); +#ifdef WIN32 + //Gtk+ does not take the non printable area into account + //http://bugzilla.gnome.org/show_bug.cgi?id=381371 + // + // This workaround translates the origin from the top left of the + // printable area to the top left of the page. + GtkPrintSettings *settings = gtk_print_operation_get_print_settings(operation); + const gchar *printerName = gtk_print_settings_get_printer(settings); + HDC hdc = CreateDC("WINSPOOL", printerName, NULL, NULL); + if (hdc) { + cairo_matrix_t mat; + int x_off = GetDeviceCaps (hdc, PHYSICALOFFSETX); + int y_off = GetDeviceCaps (hdc, PHYSICALOFFSETY); + cairo_matrix_init_translate(&mat, -x_off, -y_off); + cairo_matrix_multiply (&ctm, &ctm, &mat); + DeleteDC(hdc); + } +#endif bool ret = ctx->setSurfaceTarget (surface, true, &ctm); if (ret) { ret = renderer.setupDocument (ctx, junk->_doc, TRUE, NULL); @@ -169,12 +188,19 @@ Print::Print(SPDocument *doc, SPItem *base) : GtkPageSetup *page_setup = gtk_page_setup_new(); gdouble doc_width = sp_document_width(_doc) * PT_PER_PX; gdouble doc_height = sp_document_height(_doc) * PT_PER_PX; - GtkPaperSize *paper_size = gtk_paper_size_new_custom("custom", "custom", - doc_width, doc_height, GTK_UNIT_POINTS); + GtkPaperSize *paper_size; + if (doc_width > doc_height) { + gtk_page_setup_set_orientation (page_setup, GTK_PAGE_ORIENTATION_LANDSCAPE); + paper_size = gtk_paper_size_new_custom("custom", "custom", + doc_height, doc_width, GTK_UNIT_POINTS); + } else { + gtk_page_setup_set_orientation (page_setup, GTK_PAGE_ORIENTATION_PORTRAIT); + paper_size = gtk_paper_size_new_custom("custom", "custom", + doc_width, doc_height, GTK_UNIT_POINTS); + } + gtk_page_setup_set_paper_size (page_setup, paper_size); -#ifndef WIN32 gtk_print_operation_set_default_page_setup (_printop, page_setup); -#endif gtk_print_operation_set_use_full_page (_printop, TRUE); // set up signals diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp index e273a827c..1f708e3de 100644 --- a/src/ui/dialog/swatches.cpp +++ b/src/ui/dialog/swatches.cpp @@ -47,6 +47,8 @@ #include "display/nr-plain-stuff.h" #include "sp-gradient-reference.h" +//#define USE_DOCUMENT_PALETTE 1 + namespace Inkscape { namespace UI { namespace Dialogs { @@ -364,6 +366,7 @@ static void redirSecondaryClick( GtkMenuItem *menuitem, gpointer /*user_data*/ ) } } +#if USE_DOCUMENT_PALETTE static void editGradientImpl( SPGradient* gr ) { if ( gr ) { @@ -424,6 +427,7 @@ static void addNewGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ ) } } } +#endif // USE_DOCUMENT_PALETTE static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event, gpointer user_data) { @@ -451,6 +455,7 @@ static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event, user_data); gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); +#if USE_DOCUMENT_PALETTE child = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); popupExtras.push_back(child); @@ -484,6 +489,7 @@ static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event, gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); //popupExtras.push_back(child); gtk_widget_set_sensitive( child, FALSE ); +#endif // USE_DOCUMENT_PALETTE gtk_widget_show_all(popupMenu); } @@ -1248,6 +1254,7 @@ SwatchesPanel::SwatchesPanel(gchar const* prefsPath) : _clear->ptr = this; _remove = new ColorItem( ege::PaintDef::NONE ); _remove->ptr = this; +#if USE_DOCUMENT_PALETTE { JustForNow *docPalette = new JustForNow(); @@ -1256,6 +1263,7 @@ SwatchesPanel::SwatchesPanel(gchar const* prefsPath) : _ptr = docPalette; } +#endif // USE_DOCUMENT_PALETTE loadEmUp(); if ( !possible.empty() ) { JustForNow* first = 0; @@ -1403,6 +1411,7 @@ void SwatchesPanel::handleGradientsChange() } } +#if USE_DOCUMENT_PALETTE if ( _ptr ) { JustForNow *docPalette = reinterpret_cast<JustForNow *>(_ptr); // TODO delete pointed to objects @@ -1462,10 +1471,12 @@ void SwatchesPanel::handleGradientsChange() _rebuild(); } } +#endif // USE_DOCUMENT_PALETTE } void SwatchesPanel::_updateFromSelection() { +#if USE_DOCUMENT_PALETTE if ( _ptr ) { JustForNow *docPalette = reinterpret_cast<JustForNow *>(_ptr); @@ -1543,6 +1554,7 @@ void SwatchesPanel::_updateFromSelection() item->setState( isFill, isStroke ); } } +#endif // USE_DOCUMENT_PALETTE } void SwatchesPanel::_handleAction( int setId, int itemId ) diff --git a/src/ui/icon-names.h b/src/ui/icon-names.h index 3332dd8cf..f9a6f2a7d 100644 --- a/src/ui/icon-names.h +++ b/src/ui/icon-names.h @@ -235,7 +235,7 @@ #define INKSCAPE_ICON_IMAGE_FILTER_TURBULENCE \ "image-filter-turbulence" #define INKSCAPE_ICON_INKSCAPE \ - "inkscape" + "inkscape-logo" #define INKSCAPE_ICON_LAYER_BOTTOM \ "layer-bottom" #define INKSCAPE_ICON_LAYER_DELETE \ diff --git a/src/ui/view/edit-widget.cpp b/src/ui/view/edit-widget.cpp index b12d5adf7..770a9bf87 100644 --- a/src/ui/view/edit-widget.cpp +++ b/src/ui/view/edit-widget.cpp @@ -1071,15 +1071,15 @@ EditWidget::initStatusbar() _coord_eventbox.add (_coord_status); _tooltips.set_tip (_coord_eventbox, _("Cursor coordinates")); _coord_status.attach (*new Gtk::VSeparator(), 0,1, 0,2, Gtk::FILL,Gtk::FILL, 0,0); - _coord_status.attach (*new Gtk::Label("X:", 0.0, 0.5), 1,2, 0,1, Gtk::FILL,Gtk::FILL, 0,0); - _coord_status.attach (*new Gtk::Label("Y:", 0.0, 0.5), 1,2, 1,2, Gtk::FILL,Gtk::FILL, 0,0); + _coord_status.attach (*new Gtk::Label(_("X:"), 0.0, 0.5), 1,2, 0,1, Gtk::FILL,Gtk::FILL, 0,0); + _coord_status.attach (*new Gtk::Label(_("Y:"), 0.0, 0.5), 1,2, 1,2, Gtk::FILL,Gtk::FILL, 0,0); _coord_status_x.set_text ("0.0"); _coord_status_x.set_alignment (0.0, 0.5); _coord_status_y.set_text ("0.0"); _coord_status_y.set_alignment (0.0, 0.5); _coord_status.attach (_coord_status_x, 2,3, 0,1, Gtk::FILL,Gtk::FILL, 0,0); _coord_status.attach (_coord_status_y, 2,3, 1,2, Gtk::FILL,Gtk::FILL, 0,0); - _coord_status.attach (*new Gtk::Label("Z:", 0.0, 0.5), 3,4, 0,2, Gtk::FILL,Gtk::FILL, 0,0); + _coord_status.attach (*new Gtk::Label(_("Z:"), 0.0, 0.5), 3,4, 0,2, Gtk::FILL,Gtk::FILL, 0,0); _coord_status.attach (_zoom_status, 4,5, 0,2, Gtk::FILL,Gtk::FILL, 0,0); sp_set_font_size_smaller (static_cast<GtkWidget*>((void*)_coord_status.gobj())); _statusbar.pack_end (_coord_eventbox, false, false, 1); @@ -1247,7 +1247,7 @@ EditWidget::shutdown() _("<span weight=\"bold\" size=\"larger\">The file \"%s\" was saved with a format (%s) that may cause data loss!</span>\n\n" "Do you want to save this file as an Inkscape SVG?"), SP_DOCUMENT_NAME(doc), - Inkscape::Extension::db.get(sp_document_repr_root(doc)->attribute("inkscape:output_extension"))->get_name()); + SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE); Gtk::MessageDialog dlg (*this, markup, @@ -1551,11 +1551,10 @@ void EditWidget::initEdit (SPDocument *doc) { _desktop = new SPDesktop(); - _desktop->registerEditWidget (this); _namedview = sp_document_namedview (doc, 0); _svg_canvas.init (_desktop); - _desktop->init (_namedview, _svg_canvas.spobj()); + _desktop->init (_namedview, _svg_canvas.spobj(), this); sp_namedview_window_from_document (_desktop); sp_namedview_update_layers_from_document (_desktop); _dt2r = 1.0 / _namedview->doc_units->unittobase; diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp index 9a5364874..e7b0188d8 100644 --- a/src/ui/widget/selected-style.cpp +++ b/src/ui/widget/selected-style.cpp @@ -1252,6 +1252,7 @@ RotateableSwatch::do_motion(double by, guint modifier) { g_object_unref (bitmap); g_object_unref (mask); gdk_window_set_cursor(w->window, cr); + gdk_cursor_unref(cr); cr_set = true; } } diff --git a/src/verbs.cpp b/src/verbs.cpp index 43ddc1459..29d24c101 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -81,9 +81,9 @@ #include "ui/dialog/swatches.h" #include "ui/icon-names.h" -#ifdef WITH_INKBOARD -#include "jabber_whiteboard/session-manager.h" -#endif +//#ifdef WITH_INKBOARD +//#include "jabber_whiteboard/session-manager.h" +//#endif /** * \brief Return the name without underscores and ellipsis, for use in dialog @@ -799,14 +799,14 @@ FileVerb::perform(SPAction *action, void *data, void */*pdata*/) sp_file_import(*parent); break; case SP_VERB_FILE_EXPORT: - sp_file_export_dialog(NULL); + sp_file_export_dialog(*parent); break; case SP_VERB_FILE_IMPORT_FROM_OCAL: sp_file_import_from_ocal(*parent); break; - case SP_VERB_FILE_EXPORT_TO_OCAL: - sp_file_export_to_ocal(*parent); - break; +// case SP_VERB_FILE_EXPORT_TO_OCAL: +// sp_file_export_to_ocal(*parent); +// break; case SP_VERB_FILE_NEXT_DESKTOP: inkscape_switch_desktops_next(); break; @@ -1833,13 +1833,13 @@ DialogVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_DIALOG_ITEM: sp_item_dialog(); break; -#ifdef WITH_INKBOARD +/*#ifdef WITH_INKBOARD case SP_VERB_XMPP_CLIENT: { Inkscape::Whiteboard::SessionManager::showClient(); break; } -#endif +#endif*/ case SP_VERB_DIALOG_INPUT: sp_input_dialog(); break; @@ -2263,7 +2263,7 @@ Verb *Verb::_base_verbs[] = { new FileVerb(SP_VERB_FILE_EXPORT, "FileExport", N_("_Export Bitmap..."), N_("Export this document or a selection as a bitmap image"), INKSCAPE_ICON_DOCUMENT_EXPORT), new FileVerb(SP_VERB_FILE_IMPORT_FROM_OCAL, "FileImportFromOCAL", N_("Import From Open Clip Art Library"), N_("Import a document from Open Clip Art Library"), INKSCAPE_ICON_DOCUMENT_IMPORT_OCAL), - new FileVerb(SP_VERB_FILE_EXPORT_TO_OCAL, "FileExportToOCAL", N_("Export To Open Clip Art Library"), N_("Export this document to Open Clip Art Library"), INKSCAPE_ICON_DOCUMENT_EXPORT_OCAL), +// new FileVerb(SP_VERB_FILE_EXPORT_TO_OCAL, "FileExportToOCAL", N_("Export To Open Clip Art Library"), N_("Export this document to Open Clip Art Library"), INKSCAPE_ICON_DOCUMENT_EXPORT_OCAL), new FileVerb(SP_VERB_FILE_NEXT_DESKTOP, "NextWindow", N_("N_ext Window"), N_("Switch to the next document window"), INKSCAPE_ICON_WINDOW_NEXT), new FileVerb(SP_VERB_FILE_PREV_DESKTOP, "PrevWindow", N_("P_revious Window"), @@ -2667,10 +2667,10 @@ Verb *Verb::_base_verbs[] = { N_("Create multiple clones of selected object, arranging them into a pattern or scattering"), INKSCAPE_ICON_DIALOG_TILE_CLONES), new DialogVerb(SP_VERB_DIALOG_ITEM, "DialogObjectProperties", N_("_Object Properties..."), N_("Edit the ID, locked and visible status, and other object properties"), INKSCAPE_ICON_DIALOG_OBJECT_PROPERTIES), -#ifdef WITH_INKBOARD +/*#ifdef WITH_INKBOARD new DialogVerb(SP_VERB_XMPP_CLIENT, "DialogXmppClient", N_("_Instant Messaging..."), N_("Jabber Instant Messaging Client"), NULL), -#endif +#endif*/ new DialogVerb(SP_VERB_DIALOG_INPUT, "DialogInput", N_("_Input Devices..."), N_("Configure extended input devices, such as a graphics tablet"), INKSCAPE_ICON_DIALOG_INPUT_DEVICES), new DialogVerb(SP_VERB_DIALOG_INPUT2, "DialogInput2", N_("_Input Devices (new)..."), diff --git a/src/verbs.h b/src/verbs.h index c3b88918b..87fe27075 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -45,7 +45,7 @@ enum { SP_VERB_FILE_IMPORT, SP_VERB_FILE_EXPORT, SP_VERB_FILE_IMPORT_FROM_OCAL, /**< Import the file from Open Clip Art Library */ - SP_VERB_FILE_EXPORT_TO_OCAL, /**< Export the file to Open Clip Art Library */ +// SP_VERB_FILE_EXPORT_TO_OCAL, /**< Export the file to Open Clip Art Library */ SP_VERB_FILE_NEXT_DESKTOP, SP_VERB_FILE_PREV_DESKTOP, SP_VERB_FILE_CLOSE_VIEW, @@ -238,9 +238,9 @@ enum { SP_VERB_DIALOG_TOGGLE, SP_VERB_DIALOG_CLONETILER, SP_VERB_DIALOG_ITEM, -#ifdef WITH_INKBOARD +/*#ifdef WITH_INKBOARD SP_VERB_XMPP_CLIENT, -#endif +#endif*/ SP_VERB_DIALOG_INPUT, SP_VERB_DIALOG_INPUT2, SP_VERB_DIALOG_EXTENSIONEDITOR, diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp index f5e153a8e..e3bf1ae9c 100644 --- a/src/widgets/desktop-widget.cpp +++ b/src/widgets/desktop-widget.cpp @@ -112,6 +112,7 @@ static void sp_dtw_zoom_200 (GtkMenuItem *item, gpointer data); static void sp_dtw_zoom_page (GtkMenuItem *item, gpointer data); static void sp_dtw_zoom_drawing (GtkMenuItem *item, gpointer data); static void sp_dtw_zoom_selection (GtkMenuItem *item, gpointer data); +static void sp_dtw_sticky_zoom_toggled (GtkMenuItem *item, gpointer data); SPViewWidgetClass *dtw_parent_class; @@ -379,6 +380,7 @@ sp_desktop_widget_init (SPDesktopWidget *dtw) dtw->tt); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dtw->sticky_zoom), prefs->getBool("/options/stickyzoom/value")); gtk_box_pack_start (GTK_BOX (dtw->vscrollbar_box), dtw->sticky_zoom, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (dtw->sticky_zoom), "toggled", G_CALLBACK (sp_dtw_sticky_zoom_toggled), dtw); dtw->vadj = (GtkAdjustment *) gtk_adjustment_new (0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0); dtw->vscrollbar = gtk_vscrollbar_new (GTK_ADJUSTMENT (dtw->vadj)); gtk_box_pack_start (GTK_BOX (dtw->vscrollbar_box), dtw->vscrollbar, TRUE, TRUE, 0); @@ -499,10 +501,10 @@ sp_desktop_widget_init (SPDesktopWidget *dtw) eventbox = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (eventbox), dtw->coord_status); gtk_tooltips_set_tip (dtw->tt, eventbox, _("Cursor coordinates"), NULL); - GtkWidget *label_x = gtk_label_new("X:"); + GtkWidget *label_x = gtk_label_new(_("X:")); gtk_misc_set_alignment (GTK_MISC(label_x), 0.0, 0.5); gtk_table_attach(GTK_TABLE(dtw->coord_status), label_x, 1,2, 0,1, GTK_FILL, GTK_FILL, 0, 0); - GtkWidget *label_y = gtk_label_new("Y:"); + GtkWidget *label_y = gtk_label_new(_("Y:")); gtk_misc_set_alignment (GTK_MISC(label_y), 0.0, 0.5); gtk_table_attach(GTK_TABLE(dtw->coord_status), label_y, 1,2, 1,2, GTK_FILL, GTK_FILL, 0, 0); dtw->coord_status_x = gtk_label_new(NULL); @@ -513,7 +515,7 @@ sp_desktop_widget_init (SPDesktopWidget *dtw) gtk_misc_set_alignment (GTK_MISC(dtw->coord_status_y), 1.0, 0.5); gtk_table_attach(GTK_TABLE(dtw->coord_status), dtw->coord_status_x, 2,3, 0,1, GTK_FILL, GTK_FILL, 0, 0); gtk_table_attach(GTK_TABLE(dtw->coord_status), dtw->coord_status_y, 2,3, 1,2, GTK_FILL, GTK_FILL, 0, 0); - gtk_table_attach(GTK_TABLE(dtw->coord_status), gtk_label_new("Z:"), 3,4, 0,2, GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach(GTK_TABLE(dtw->coord_status), gtk_label_new(_("Z:")), 3,4, 0,2, GTK_FILL, GTK_FILL, 0, 0); gtk_table_attach(GTK_TABLE(dtw->coord_status), dtw->zoom_status, 4,5, 0,2, GTK_FILL, GTK_FILL, 0, 0); sp_set_font_size_smaller (dtw->coord_status); gtk_box_pack_end (GTK_BOX (statusbar_tail), eventbox, FALSE, FALSE, 1); @@ -553,6 +555,8 @@ sp_desktop_widget_init (SPDesktopWidget *dtw) gtk_box_pack_start (GTK_BOX (dtw->statusbar), dtw->select_status_eventbox, TRUE, TRUE, 0); gtk_widget_show_all (dtw->vbox); + + gtk_widget_grab_focus (GTK_WIDGET(dtw->canvas)); } /** @@ -659,7 +663,9 @@ sp_desktop_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) /* Find new visible area */ Geom::Rect newarea = dtw->desktop->get_display_area(); /* Calculate adjusted zoom */ - zoom *= sqrt(newarea.area() / area.area()); + double oldshortside = MIN( area.width(), area.height()); + double newshortside = MIN(newarea.width(), newarea.height()); + zoom *= newshortside / oldshortside; } dtw->desktop->zoom_absolute(area.midpoint()[Geom::X], area.midpoint()[Geom::Y], zoom); @@ -888,7 +894,7 @@ SPDesktopWidget::shutdown() _("<span weight=\"bold\" size=\"larger\">The file \"%s\" was saved with a format (%s) that may cause data loss!</span>\n\n" "Do you want to save this file as an Inkscape SVG?"), SP_DOCUMENT_NAME(doc), - Inkscape::Extension::db.get(sp_document_repr_root(doc)->attribute("inkscape:output_extension"))->get_name()); + SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE); // fix for bug 1767940: GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(GTK_MESSAGE_DIALOG(dialog)->label), GTK_CAN_FOCUS); @@ -915,7 +921,7 @@ SPDesktopWidget::shutdown() Gtk::Window *window = (Gtk::Window*)gtk_object_get_data (GTK_OBJECT(this), "window"); - if (sp_file_save_dialog(*window, doc)) { + if (sp_file_save_dialog(*window, doc, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG)) { sp_document_unref(doc); } else { // save dialog cancelled or save failed sp_document_unref(doc); @@ -1277,11 +1283,19 @@ SPDesktopWidget::setToolboxFocusTo (const gchar* label) void SPDesktopWidget::setToolboxAdjustmentValue (gchar const *id, double value) { + GtkAdjustment *a = NULL; gpointer hb = sp_search_by_data_recursive (aux_toolbox, (gpointer) id); - if (hb && GTK_IS_WIDGET(hb) && GTK_IS_SPIN_BUTTON(hb)) { - GtkAdjustment *a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(hb)); - gtk_adjustment_set_value (a, value); + if (hb && GTK_IS_WIDGET(hb)) { + if (GTK_IS_SPIN_BUTTON(hb)) + a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(hb)); + else if (GTK_IS_RANGE(hb)) + a = gtk_range_get_adjustment (GTK_RANGE(hb)); } + + if (a) + gtk_adjustment_set_value (a, value); + else + g_warning ("Could not find GtkAdjustment for %s\n", id); } void @@ -1325,8 +1339,7 @@ sp_desktop_widget_new (SPNamedView *namedview) dtw->desktop = new SPDesktop(); dtw->stub = new SPDesktopWidget::WidgetStub (dtw); - dtw->desktop->registerEditWidget (dtw->stub); - dtw->desktop->init (namedview, dtw->canvas); + dtw->desktop->init (namedview, dtw->canvas, dtw->stub); inkscape_add_desktop (dtw->desktop); // Add the shape geometry to libavoid for autorouting connectors. @@ -1613,6 +1626,13 @@ sp_dtw_zoom_selection (GtkMenuItem */*item*/, gpointer data) static_cast<SPDesktop*>(data)->zoom_selection(); } +static void +sp_dtw_sticky_zoom_toggled (GtkMenuItem *, gpointer data) +{ + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET(data); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/stickyzoom/value", SP_BUTTON_IS_DOWN(dtw->sticky_zoom)); +} void diff --git a/src/widgets/gradient-vector.cpp b/src/widgets/gradient-vector.cpp index 1f0c07754..c884604a2 100644 --- a/src/widgets/gradient-vector.cpp +++ b/src/widgets/gradient-vector.cpp @@ -793,7 +793,6 @@ sp_gradient_vector_widget_new (SPGradient *gradient, SPStop *select_stop) gtk_widget_show (w); gtk_box_pack_start (GTK_BOX (vb), w, TRUE, TRUE, PAD); - gtk_object_set_data (GTK_OBJECT (vb), "gradient", gradient); sp_repr_add_listener (SP_OBJECT_REPR(gradient), &grad_edit_dia_repr_events, vb); GtkTooltips *tt = gtk_tooltips_new (); @@ -1013,6 +1012,8 @@ sp_gradient_vector_widget_load_gradient (GtkWidget *widget, SPGradient *gradient g_object_set_data (G_OBJECT (widget), "gradient", gradient); if (gradient) { + gtk_widget_set_sensitive (widget, TRUE); + sp_gradient_ensure_vector (gradient); GtkOptionMenu *mnu = (GtkOptionMenu *)g_object_get_data (G_OBJECT(widget), "stopmenu"); @@ -1026,22 +1027,23 @@ sp_gradient_vector_widget_load_gradient (GtkWidget *widget, SPGradient *gradient SPColor color( SP_RGBA32_R_F (c), SP_RGBA32_G_F (c), SP_RGBA32_B_F (c) ); // set color csel->base->setColor( color ); - } - /* Fill preview */ - GtkWidget *w = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(widget), "preview")); - sp_gradient_image_set_gradient (SP_GRADIENT_IMAGE (w), gradient); + /* Fill preview */ + GtkWidget *w = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(widget), "preview")); + sp_gradient_image_set_gradient (SP_GRADIENT_IMAGE (w), gradient); - GtkWidget *mnu = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(widget), "stopmenu")); - update_stop_list (GTK_WIDGET(mnu), gradient, NULL); + update_stop_list (GTK_WIDGET(mnu), gradient, NULL); - // Once the user edits a gradient, it stops being auto-collectable - if (SP_OBJECT_REPR(gradient)->attribute("inkscape:collect")) { - SPDocument *document = SP_OBJECT_DOCUMENT (gradient); - bool saved = sp_document_get_undo_sensitive(document); - sp_document_set_undo_sensitive (document, false); - SP_OBJECT_REPR(gradient)->setAttribute("inkscape:collect", NULL); - sp_document_set_undo_sensitive (document, saved); + // Once the user edits a gradient, it stops being auto-collectable + if (SP_OBJECT_REPR(gradient)->attribute("inkscape:collect")) { + SPDocument *document = SP_OBJECT_DOCUMENT (gradient); + bool saved = sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive (document, false); + SP_OBJECT_REPR(gradient)->setAttribute("inkscape:collect", NULL); + sp_document_set_undo_sensitive (document, saved); + } + } else { // no gradient, disable everything + gtk_widget_set_sensitive (widget, FALSE); } blocked = FALSE; @@ -1082,10 +1084,18 @@ sp_gradient_vector_widget_destroy (GtkObject *object, gpointer /*data*/) gradient = (GObject*)g_object_get_data (G_OBJECT (object), "gradient"); - if (gradient && SP_OBJECT_REPR(gradient)) { - /* Remove signals connected to us */ - /* fixme: may use _connect_while_alive as well */ + sigc::connection *release_connection = (sigc::connection *)g_object_get_data(G_OBJECT(object), "gradient_release_connection"); + sigc::connection *modified_connection = (sigc::connection *)g_object_get_data(G_OBJECT(object), "gradient_modified_connection"); + + if (gradient) { + g_assert( release_connection != NULL ); + g_assert( modified_connection != NULL ); + release_connection->disconnect(); + modified_connection->disconnect(); sp_signal_disconnect_by_data (gradient, object); + } + + if (gradient && SP_OBJECT_REPR(gradient)) { sp_repr_remove_listener_by_data (SP_OBJECT_REPR(gradient), object); } } diff --git a/src/widgets/icon.cpp b/src/widgets/icon.cpp index 6be4b49b3..5824b102c 100644 --- a/src/widgets/icon.cpp +++ b/src/widgets/icon.cpp @@ -381,7 +381,7 @@ static void setupLegacyNaming() { legacyNames["text-unkern"] ="remove_manual_kerns"; legacyNames["help-keyboard-shortcuts"] ="help_keys"; legacyNames["help-contents"] ="help_tutorials"; - legacyNames["inkscape"] ="inkscape_options"; + legacyNames["inkscape-logo"] ="inkscape_options"; legacyNames["dialog-memory"] ="about_memory"; legacyNames["tool-pointer"] ="draw_select"; legacyNames["tool-node-editor"] ="draw_node"; diff --git a/src/widgets/mappings.xml b/src/widgets/mappings.xml index f142c450c..2de3ff545 100644 --- a/src/widgets/mappings.xml +++ b/src/widgets/mappings.xml @@ -113,7 +113,7 @@ <!-- help menu --> <remap id='help_keys' newid='help-keyboard-shortcuts'/> <remap id='help_tutorials' newid='help-contents'/> - <remap id='inkscape_options' newid='inkscape'/> + <remap id='inkscape_options' newid='inkscape-logo'/> <remap id='about_memory' newid='dialog-memory'/> <!-- tools --> diff --git a/src/widgets/sp-color-icc-selector.cpp b/src/widgets/sp-color-icc-selector.cpp index b18290923..a10d2380c 100644 --- a/src/widgets/sp-color-icc-selector.cpp +++ b/src/widgets/sp-color-icc-selector.cpp @@ -3,7 +3,7 @@ #endif #include <math.h> #include <gtk/gtkbutton.h> -#include <gtk/gtkcombobox.h> +#include <gtk/gtk.h> #include <gtk/gtksignal.h> #include <gtk/gtklabel.h> #include <gtk/gtktable.h> @@ -303,7 +303,7 @@ void ColorICCSelector::init() _profileSel = gtk_combo_box_new_text(); - gtk_combo_box_append_text( GTK_COMBO_BOX(_profileSel), "<none>" ); + gtk_combo_box_append_text( GTK_COMBO_BOX(_profileSel), _("<none>") ); gtk_widget_show( _profileSel ); gtk_combo_box_set_active( GTK_COMBO_BOX(_profileSel), 0 ); gtk_table_attach( GTK_TABLE(t), _profileSel, 1, 2, row, row + 1, GTK_FILL, GTK_FILL, XPAD, YPAD ); @@ -576,7 +576,7 @@ void ColorICCSelector::_profilesChanged( std::string const & name ) gtk_combo_box_remove_text( combo, 0 ); } - gtk_combo_box_append_text( combo, "<none>"); + gtk_combo_box_append_text( combo, _("<none>")); gtk_combo_box_set_active( combo, 0 ); @@ -637,14 +637,17 @@ void ColorICCSelector::_colorChanged() tmp[i] = val * 0x0ffff; } guchar post[4] = {0,0,0,0}; - cmsDoTransform( _prof->getTransfToSRGB8(), tmp, post, 1 ); - guint32 other = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255 ); - if ( other != _color.toRGBA32(255) ) { - _fixupNeeded = other; - gtk_widget_set_sensitive( _fixupBtn, TRUE ); + cmsHTRANSFORM trans = _prof->getTransfToSRGB8(); + if ( trans ) { + cmsDoTransform( trans, tmp, post, 1 ); + guint32 other = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255 ); + if ( other != _color.toRGBA32(255) ) { + _fixupNeeded = other; + gtk_widget_set_sensitive( _fixupBtn, TRUE ); #ifdef DEBUG_LCMS - g_message("Color needs to change 0x%06x to 0x%06x", _color.toRGBA32(255) >> 8, other >> 8 ); + g_message("Color needs to change 0x%06x to 0x%06x", _color.toRGBA32(255) >> 8, other >> 8 ); #endif // DEBUG_LCMS + } } } #else @@ -753,7 +756,7 @@ void ColorICCSelector::_updateSliders( gint ignore ) gtk_adjustment_set_value( _fooAdj[i], val ); } - if ( _prof->getTransfToSRGB8() ) { + if ( _prof && _prof->getTransfToSRGB8() ) { for ( guint i = 0; i < _profChannelCount; i++ ) { if ( static_cast<gint>(i) != ignore ) { icUInt16Number* scratch = getScratch(); @@ -773,8 +776,11 @@ void ColorICCSelector::_updateSliders( gint ignore ) } } - cmsDoTransform( _prof->getTransfToSRGB8(), scratch, _fooMap[i], 1024 ); - sp_color_slider_set_map( SP_COLOR_SLIDER(_fooSlider[i]), _fooMap[i] ); + cmsHTRANSFORM trans = _prof->getTransfToSRGB8(); + if ( trans ) { + cmsDoTransform( trans, scratch, _fooMap[i], 1024 ); + sp_color_slider_set_map( SP_COLOR_SLIDER(_fooSlider[i]), _fooMap[i] ); + } } } } @@ -840,7 +846,10 @@ void ColorICCSelector::_adjustmentChanged( GtkAdjustment *adjustment, SPColorICC } guchar post[4] = {0,0,0,0}; - cmsDoTransform( iccSelector->_prof->getTransfToSRGB8(), tmp, post, 1 ); + cmsHTRANSFORM trans = iccSelector->_prof->getTransfToSRGB8(); + if ( trans ) { + cmsDoTransform( trans, tmp, post, 1 ); + } SPColor other( SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255) ); other.icc = new SVGICCColor(); diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index 9ac009a84..e0fe9bfd1 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -2384,8 +2384,10 @@ static void sp_stb_proportion_value_changed( GtkAdjustment *adj, GObject *dataKl SPDesktop *desktop = (SPDesktop *) g_object_get_data( dataKludge, "desktop" ); if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setDouble("/tools/shapes/star/proportion", adj->value); + if (!IS_NAN(adj->value)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/tools/shapes/star/proportion", adj->value); + } } // quit if run by the attr_changed listener @@ -4927,7 +4929,7 @@ sp_arctb_startend_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *v if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setDouble(Glib::ustring("/tools/shapes/arc") + value_name, (adj->value * M_PI)/ 180); + prefs->setDouble(Glib::ustring("/tools/shapes/arc/") + value_name, adj->value); } // quit if run by the attr_changed listener @@ -5571,7 +5573,7 @@ static void sp_lpetool_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActi gtk_list_store_set( model, &iter, 0, _("All inactive"), 1, _("No geometric tool is active"), - 2, _("draw-geometry-inactive"), + 2, "draw-geometry-inactive", -1 ); Inkscape::LivePathEffect::EffectType type; @@ -5695,7 +5697,7 @@ static void sp_lpetool_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActi static void sp_erc_width_value_changed( GtkAdjustment *adj, GObject *tbl ) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setDouble( "/tools/eraser/width", adj->value * 0.01 ); + prefs->setDouble( "/tools/eraser/width", adj->value ); update_presets_list(tbl); } @@ -5735,7 +5737,7 @@ static void sp_eraser_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActio GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-eraser", 1, 100, 1.0, 10.0, labels, values, G_N_ELEMENTS(labels), - sp_erc_width_value_changed, 0.01, 0, 100 ); + sp_erc_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 ); @@ -5939,7 +5941,7 @@ sp_text_toolbox_selection_changed (Inkscape::Selection */*selection*/, GObject * GtkToggleButton *button = GTK_TOGGLE_BUTTON (g_object_get_data (G_OBJECT (tbl), "style-bold")); gboolean active = gtk_toggle_button_get_active (button); - gboolean check = (query->font_weight.computed >= SP_CSS_FONT_WEIGHT_700); + gboolean check = ((query->font_weight.computed >= SP_CSS_FONT_WEIGHT_700) && (query->font_weight.computed != SP_CSS_FONT_WEIGHT_NORMAL) && (query->font_weight.computed != SP_CSS_FONT_WEIGHT_LIGHTER)); if (active != check) { @@ -6087,8 +6089,8 @@ sp_text_toolbox_family_changed (GtkComboBoxEntry *, // If querying returned nothing, set the default style of the tool (for new texts) if (result_fontspec == QUERY_STYLE_NOTHING) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/text/style", css); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->mergeStyle("/tools/text/style", css); sp_text_edit_dialog_default_set_insensitive (); //FIXME: Replace trough a verb } else @@ -6162,7 +6164,7 @@ sp_text_toolbox_anchoring_toggled (GtkRadioButton *button, if (result_numbers == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); @@ -6206,17 +6208,31 @@ sp_text_toolbox_style_toggled (GtkToggleButton *button, fontFromStyle->Unref(); } + bool nochange = true; switch (prop) { case 0: { if (!fontSpec.empty()) { newFontSpec = font_factory::Default()->FontSpecificationSetBold(fontSpec, active); + if (!newFontSpec.empty()) { + // Don't even set the bold if the font didn't exist on the system + sp_repr_css_set_property (css, "font-weight", active ? "bold" : "normal" ); + nochange = false; + } } - if (fontSpec != newFontSpec) { - // Don't even set the bold if the font didn't exist on the system - sp_repr_css_set_property (css, "font-weight", active ? "bold" : "normal" ); + // set or reset the button according + if(nochange) { + gboolean check = gtk_toggle_button_get_active (button); + + if (active != check) + { + g_object_set_data (G_OBJECT (button), "block", gpointer(1)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), active); + g_object_set_data (G_OBJECT (button), "block", gpointer(0)); + } } + break; } @@ -6224,10 +6240,21 @@ sp_text_toolbox_style_toggled (GtkToggleButton *button, { if (!fontSpec.empty()) { newFontSpec = font_factory::Default()->FontSpecificationSetItalic(fontSpec, active); + if (!newFontSpec.empty()) { + // Don't even set the italic if the font didn't exist on the system + sp_repr_css_set_property (css, "font-style", active ? "italic" : "normal"); + nochange = false; + } } - if (fontSpec != newFontSpec) { - // Don't even set the italic if the font didn't exist on the system - sp_repr_css_set_property (css, "font-style", active ? "italic" : "normal"); + if(nochange) { + gboolean check = gtk_toggle_button_get_active (button); + + if (active != check) + { + g_object_set_data (G_OBJECT (button), "block", gpointer(1)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), active); + g_object_set_data (G_OBJECT (button), "block", gpointer(0)); + } } break; } @@ -6241,7 +6268,7 @@ sp_text_toolbox_style_toggled (GtkToggleButton *button, if (result_fontspec == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); @@ -6290,7 +6317,7 @@ sp_text_toolbox_orientation_toggled (GtkRadioButton *button, if (result_numbers == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_desktop_set_style (desktop, css, true, true); @@ -6402,7 +6429,7 @@ sp_text_toolbox_size_changed (GtkComboBox *cbox, if (result_numbers == QUERY_STYLE_NOTHING) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/text/style", css); + prefs->mergeStyle("/tools/text/style", css); } sp_style_unref(query); diff --git a/src/xml/rebase-hrefs.cpp b/src/xml/rebase-hrefs.cpp index ec43bb178..c3876725d 100644 --- a/src/xml/rebase-hrefs.cpp +++ b/src/xml/rebase-hrefs.cpp @@ -201,6 +201,9 @@ Inkscape::XML::calc_abs_doc_base(gchar const *const doc_base) */ void Inkscape::XML::rebase_hrefs(SPDocument *const doc, gchar const *const new_base, bool const spns) { + if (!doc->base) + return; + gchar *const old_abs_base = calc_abs_doc_base(doc->base); gchar *const new_abs_base = calc_abs_doc_base(new_base); diff --git a/src/zoom-context.cpp b/src/zoom-context.cpp index 2f0185731..f8212069e 100644 --- a/src/zoom-context.cpp +++ b/src/zoom-context.cpp @@ -18,6 +18,7 @@ #include "macros.h" #include "rubberband.h" +#include "display/sp-canvas-util.h" #include "desktop.h" #include "pixmaps/cursor-zoom.xpm" #include "pixmaps/cursor-zoom-out.xpm" @@ -86,7 +87,14 @@ static void sp_zoom_context_init (SPZoomContext *zoom_context) static void sp_zoom_context_finish (SPEventContext *ec) { + SPZoomContext *zc = SP_ZOOM_CONTEXT(ec); + ec->enableGrDrag(false); + + if (zc->grabbed) { + sp_canvas_item_ungrab(zc->grabbed, GDK_CURRENT_TIME); + zc->grabbed = NULL; + } } static void sp_zoom_context_setup(SPEventContext *ec) @@ -119,6 +127,8 @@ static gint sp_zoom_context_root_handler(SPEventContext *event_context, GdkEvent { SPDesktop *desktop = event_context->desktop; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + SPZoomContext *zc = SP_ZOOM_CONTEXT(event_context); tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10); @@ -147,6 +157,12 @@ static gint sp_zoom_context_root_handler(SPEventContext *event_context, GdkEvent desktop->zoom_relative_keep_point(button_dt, zoom_rel); ret = TRUE; } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + zc->grabbed = SP_CANVAS_ITEM(desktop->acetate); + break; } @@ -188,6 +204,12 @@ static gint sp_zoom_context_root_handler(SPEventContext *event_context, GdkEvent ret = TRUE; } Inkscape::Rubberband::get(desktop)->stop(); + + if (zc->grabbed) { + sp_canvas_item_ungrab(zc->grabbed, event->button.time); + zc->grabbed = NULL; + } + xp = yp = 0; escaped = false; break; diff --git a/src/zoom-context.h b/src/zoom-context.h index aadba9551..133267135 100644 --- a/src/zoom-context.h +++ b/src/zoom-context.h @@ -24,6 +24,7 @@ class SPZoomContextClass; struct SPZoomContext { SPEventContext event_context; + SPCanvasItem *grabbed; }; struct SPZoomContextClass { |
