Code

Added test script I thought was already added.
[inkscape.git] / src / object-edit.cpp
1 #define __SP_OBJECT_EDIT_C__
3 /*
4  * Node editing extension to objects
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Mitsuru Oka
9  *   Maximilian Albert <maximilian.albert@gmail.com>
10  *
11  * Licensed under GNU GPL
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
20 #include "sp-item.h"
21 #include "sp-rect.h"
22 #include "box3d.h"
23 #include "sp-ellipse.h"
24 #include "sp-star.h"
25 #include "sp-spiral.h"
26 #include "sp-offset.h"
27 #include "sp-flowtext.h"
28 #include "preferences.h"
29 #include "style.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "sp-namedview.h"
33 #include "live_effects/effect.h"
35 #include "sp-pattern.h"
36 #include "sp-path.h"
38 #include <glibmm/i18n.h>
40 #include "object-edit.h"
42 #include <libnr/nr-scale-ops.h>
44 #include "xml/repr.h"
46 #include "2geom/isnan.h"
48 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
50 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
51 {
52     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
54     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
55     effect->addHandles(knot_holder, desktop, item);
57     return knot_holder;
58 }
60 KnotHolder *
61 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
62 {
63     KnotHolder *knotholder = NULL;
65     if (SP_IS_LPE_ITEM(item) &&
66         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
67         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
68         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
69         knotholder = sp_lpe_knot_holder(item, desktop);
70     } else if (SP_IS_RECT(item)) {
71         knotholder = new RectKnotHolder(desktop, item, NULL);
72     } else if (SP_IS_BOX3D(item)) {
73         knotholder = new Box3DKnotHolder(desktop, item, NULL);
74     } else if (SP_IS_ARC(item)) {
75         knotholder = new ArcKnotHolder(desktop, item, NULL);
76     } else if (SP_IS_STAR(item)) {
77         knotholder = new StarKnotHolder(desktop, item, NULL);
78     } else if (SP_IS_SPIRAL(item)) {
79         knotholder = new SpiralKnotHolder(desktop, item, NULL);
80     } else if (SP_IS_OFFSET(item)) {
81         knotholder = new OffsetKnotHolder(desktop, item, NULL);
82     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
83         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
84     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
85                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
86         knotholder = new KnotHolder(desktop, item, NULL);
87         knotholder->add_pattern_knotholder();
88     }
90     return knotholder;
91 }
93 /* SPRect */
95 /* handle for horizontal rounding radius */
96 class RectKnotHolderEntityRX : public KnotHolderEntity {
97 public:
98     virtual Geom::Point knot_get();
99     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
100     virtual void knot_click(guint state);
101 };
103 /* handle for vertical rounding radius */
104 class RectKnotHolderEntityRY : public KnotHolderEntity {
105 public:
106     virtual Geom::Point knot_get();
107     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
108     virtual void knot_click(guint state);
109 };
111 /* handle for width/height adjustment */
112 class RectKnotHolderEntityWH : public KnotHolderEntity {
113 public:
114     virtual Geom::Point knot_get();
115     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
117 protected:
118     void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
119 };
121 /* handle for x/y adjustment */
122 class RectKnotHolderEntityXY : public KnotHolderEntity {
123 public:
124     virtual Geom::Point knot_get();
125     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
126 };
128 Geom::Point
129 RectKnotHolderEntityRX::knot_get()
131     SPRect *rect = SP_RECT(item);
133     return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
136 void
137 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
139     SPRect *rect = SP_RECT(item);
141     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
142     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
143     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
144     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
146     if (state & GDK_CONTROL_MASK) {
147         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
148         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
149         rect->rx._set = rect->ry._set = true;
151     } else {
152         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
153         rect->rx._set = true;
154     }
156     update_knot();
158     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
161 void
162 RectKnotHolderEntityRX::knot_click(guint state)
164     SPRect *rect = SP_RECT(item);
166     if (state & GDK_SHIFT_MASK) {
167         /* remove rounding from rectangle */
168         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
169         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
170     } else if (state & GDK_CONTROL_MASK) {
171         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
172         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
173     }
175     update_knot();
178 Geom::Point
179 RectKnotHolderEntityRY::knot_get()
181     SPRect *rect = SP_RECT(item);
183     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
186 void
187 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
189     SPRect *rect = SP_RECT(item);
191     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
192     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
193     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
194     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
196     if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
197                                     // resulting in a perfect circle (and not an ellipse)
198         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
199         rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
200         rect->ry._set = rect->rx._set = true;
201     } else {
202         if (!rect->rx._set || rect->rx.computed == 0) {
203             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
204                                       0.0,
205                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
206         } else {
207             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
208                                       0.0,
209                                       rect->height.computed / 2.0);
210         }
212         rect->ry._set = true;
213     }
215     update_knot();
217     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
220 void
221 RectKnotHolderEntityRY::knot_click(guint state)
223     SPRect *rect = SP_RECT(item);
225     if (state & GDK_SHIFT_MASK) {
226         /* remove rounding */
227         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
228         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
229     } else if (state & GDK_CONTROL_MASK) {
230         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
231         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
232     }
235 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
237 static void sp_rect_clamp_radii(SPRect *rect)
239     // clamp rounding radii so that they do not exceed width/height
240     if (2 * rect->rx.computed > rect->width.computed) {
241         rect->rx.computed = 0.5 * rect->width.computed;
242         rect->rx._set = true;
243     }
244     if (2 * rect->ry.computed > rect->height.computed) {
245         rect->ry.computed = 0.5 * rect->height.computed;
246         rect->ry._set = true;
247     }
250 Geom::Point
251 RectKnotHolderEntityWH::knot_get()
253     SPRect *rect = SP_RECT(item);
255     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
258 void
259 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
261     SPRect *rect = SP_RECT(item);
263     Geom::Point s = p;
265     if (state & GDK_CONTROL_MASK) {
266         // original width/height when drag started
267         gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
268         gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
270         //original ratio
271         gdouble ratio = (w_orig / h_orig);
273         // mouse displacement since drag started
274         gdouble minx = p[Geom::X] - origin[Geom::X];
275         gdouble miny = p[Geom::Y] - origin[Geom::Y];
277         Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
279         if (fabs(minx) > fabs(miny)) {
280                 // snap to horizontal or diagonal
281             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
282                 // closer to the diagonal and in same-sign quarters, change both using ratio
283                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
284                                 minx = s[Geom::X] - origin[Geom::X];
285                                 miny = s[Geom::Y] - origin[Geom::Y];
286                                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
287             } else {
288                 // closer to the horizontal, change only width, height is h_orig
289                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)));
290                                 minx = s[Geom::X] - origin[Geom::X];
291                                 miny = s[Geom::Y] - origin[Geom::Y];
292                                 rect->height.computed = MAX(h_orig, 0);
293             }
294             rect->width.computed = MAX(w_orig + minx, 0);
296         } else {
297             // snap to vertical or diagonal
298             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
299                 // closer to the diagonal and in same-sign quarters, change both using ratio
300                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
301                                 minx = s[Geom::X] - origin[Geom::X];
302                                 miny = s[Geom::Y] - origin[Geom::Y];
303                         rect->width.computed = MAX(w_orig + miny * ratio, 0);
304             } else {
305                 // closer to the vertical, change only height, width is w_orig
306                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)));
307                                 minx = s[Geom::X] - origin[Geom::X];
308                                 miny = s[Geom::Y] - origin[Geom::Y];
309                                 rect->width.computed = MAX(w_orig, 0);
310             }
311             rect->height.computed = MAX(h_orig + miny, 0);
313         }
315         rect->width._set = rect->height._set = true;
317     } else {
318         // move freely
319         s = snap_knot_position(p);
320         rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
321         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
322         rect->width._set = rect->height._set = true;
323     }
325     sp_rect_clamp_radii(rect);
327     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330 void
331 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
333     set_internal(p, origin, state);
334     update_knot();
337 Geom::Point
338 RectKnotHolderEntityXY::knot_get()
340     SPRect *rect = SP_RECT(item);
342     return Geom::Point(rect->x.computed, rect->y.computed);
345 void
346 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
348     SPRect *rect = SP_RECT(item);
350     // opposite corner (unmoved)
351     gdouble opposite_x = (rect->x.computed + rect->width.computed);
352     gdouble opposite_y = (rect->y.computed + rect->height.computed);
354     // original width/height when drag started
355     gdouble w_orig = opposite_x - origin[Geom::X];
356     gdouble h_orig = opposite_y - origin[Geom::Y];
358     Geom::Point s = p;
359     Geom::Point p_handle(rect->x.computed, rect->y.computed);
361     // mouse displacement since drag started
362     gdouble minx = p[Geom::X] - origin[Geom::X];
363     gdouble miny = p[Geom::Y] - origin[Geom::Y];
365     if (state & GDK_CONTROL_MASK) {
366         //original ratio
367         gdouble ratio = (w_orig / h_orig);
369         if (fabs(minx) > fabs(miny)) {
370                 // snap to horizontal or diagonal
371             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
372                 // closer to the diagonal and in same-sign quarters, change both using ratio
373                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
374                                 minx = s[Geom::X] - origin[Geom::X];
375                                 miny = s[Geom::Y] - origin[Geom::Y];
376                 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
377                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
378             } else {
379                 // closer to the horizontal, change only width, height is h_orig
380                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)));
381                                 minx = s[Geom::X] - origin[Geom::X];
382                                 miny = s[Geom::Y] - origin[Geom::Y];
383                 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
384                 rect->height.computed = MAX(h_orig, 0);
385             }
386             rect->x.computed = MIN(s[Geom::X], opposite_x);
387             rect->width.computed = MAX(w_orig - minx, 0);
388         } else {
389             // snap to vertical or diagonal
390                 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
391                 // closer to the diagonal and in same-sign quarters, change both using ratio
392                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
393                                 minx = s[Geom::X] - origin[Geom::X];
394                                 miny = s[Geom::Y] - origin[Geom::Y];
395                                 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
396                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
397             } else {
398                 // closer to the vertical, change only height, width is w_orig
399                 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)));
400                                 minx = s[Geom::X] - origin[Geom::X];
401                                 miny = s[Geom::Y] - origin[Geom::Y];
402                                 rect->x.computed = MIN(origin[Geom::X], opposite_x);
403                 rect->width.computed = MAX(w_orig, 0);
404             }
405             rect->y.computed = MIN(s[Geom::Y], opposite_y);
406                         rect->height.computed = MAX(h_orig - miny, 0);
407         }
409         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
411     } else {
412         // move freely
413         s = snap_knot_position(p);
414         minx = s[Geom::X] - origin[Geom::X];
415                 miny = s[Geom::Y] - origin[Geom::Y];
417         rect->x.computed = MIN(s[Geom::X], opposite_x);
418         rect->width.computed = MAX(w_orig - minx, 0);
419         rect->y.computed = MIN(s[Geom::Y], opposite_y);
420         rect->height.computed = MAX(h_orig - miny, 0);
421         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
422     }
424     sp_rect_clamp_radii(rect);
426     update_knot();
428     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
431 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
432     KnotHolder(desktop, item, relhandler)
434     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
435     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
436     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
437     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
438     entity_rx->create(desktop, item, this,
439                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
440                         "to make the vertical radius the same"),
441                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
442     entity_ry->create(desktop, item, this,
443                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
444                         "to make the horizontal radius the same"),
445                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
446     entity_wh->create(desktop, item, this,
447                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
448                         "to lock ratio or stretch in one dimension only"),
449                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
450     entity_xy->create(desktop, item, this,
451                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
452                         "to lock ratio or stretch in one dimension only"),
453                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
454     entity.push_back(entity_rx);
455     entity.push_back(entity_ry);
456     entity.push_back(entity_wh);
457     entity.push_back(entity_xy);
459     add_pattern_knotholder();
462 /* Box3D (= the new 3D box structure) */
464 class Box3DKnotHolderEntity : public KnotHolderEntity {
465 public:
466     virtual Geom::Point knot_get() = 0;
467     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
469     Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
470     void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
471 };
473 Geom::Point
474 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
476     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
479 void
480 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
482     Geom::Point const s = snap_knot_position(new_pos);
484     g_assert(item != NULL);
485     SPBox3D *box = SP_BOX3D(item);
486     Geom::Matrix const i2d (sp_item_i2d_affine (item));
488     Box3D::Axis movement;
489     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
490         movement = Box3D::XY;
491     } else {
492         movement = Box3D::Z;
493     }
495     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
496     box3d_set_z_orders(box);
497     box3d_position_set(box);
500 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
501 public:
502     virtual Geom::Point knot_get();
503     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
504 };
506 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
507 public:
508     virtual Geom::Point knot_get();
509     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
510 };
512 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
513 public:
514     virtual Geom::Point knot_get();
515     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
516 };
518 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
519 public:
520     virtual Geom::Point knot_get();
521     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
522 };
524 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
525 public:
526     virtual Geom::Point knot_get();
527     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
528 };
530 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
531 public:
532     virtual Geom::Point knot_get();
533     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
534 };
536 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
537 public:
538     virtual Geom::Point knot_get();
539     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
540 };
542 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
543 public:
544     virtual Geom::Point knot_get();
545     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
546 };
548 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
549 public:
550     virtual Geom::Point knot_get();
551     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
552 };
554 Geom::Point
555 Box3DKnotHolderEntity0::knot_get()
557     return knot_get_generic(item, 0);
560 Geom::Point
561 Box3DKnotHolderEntity1::knot_get()
563     return knot_get_generic(item, 1);
566 Geom::Point
567 Box3DKnotHolderEntity2::knot_get()
569     return knot_get_generic(item, 2);
572 Geom::Point
573 Box3DKnotHolderEntity3::knot_get()
575     return knot_get_generic(item, 3);
578 Geom::Point
579 Box3DKnotHolderEntity4::knot_get()
581     return knot_get_generic(item, 4);
584 Geom::Point
585 Box3DKnotHolderEntity5::knot_get()
587     return knot_get_generic(item, 5);
590 Geom::Point
591 Box3DKnotHolderEntity6::knot_get()
593     return knot_get_generic(item, 6);
596 Geom::Point
597 Box3DKnotHolderEntity7::knot_get()
599     return knot_get_generic(item, 7);
602 Geom::Point
603 Box3DKnotHolderEntityCenter::knot_get()
605     return box3d_get_center_screen(SP_BOX3D(item));
608 void
609 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
611     knot_set_generic(item, 0, new_pos, state);
614 void
615 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
617     knot_set_generic(item, 1, new_pos, state);
620 void
621 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
623     knot_set_generic(item, 2, new_pos, state);
626 void
627 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
629     knot_set_generic(item, 3, new_pos, state);
632 void
633 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
635     knot_set_generic(item, 4, new_pos, state);
638 void
639 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
641     knot_set_generic(item, 5, new_pos, state);
644 void
645 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
647     knot_set_generic(item, 6, new_pos, state);
650 void
651 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
653     knot_set_generic(item, 7, new_pos, state);
656 void
657 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
659     Geom::Point const s = snap_knot_position(new_pos);
661     SPBox3D *box = SP_BOX3D(item);
662     Geom::Matrix const i2d (sp_item_i2d_affine (item));
664     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
665                       state & GDK_CONTROL_MASK);
667     box3d_set_z_orders(box);
668     box3d_position_set(box);
671 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
672     KnotHolder(desktop, item, relhandler)
674     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
675     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
676     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
677     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
678     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
679     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
680     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
681     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
682     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
684     entity_corner0->create(desktop, item, this,
685                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
686                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
687     entity_corner1->create(desktop, item, this,
688                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
689                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
690     entity_corner2->create(desktop, item, this,
691                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
692                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
693     entity_corner3->create(desktop, item, this,
694                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
695                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
696     entity_corner4->create(desktop, item, this,
697                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
698                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
699     entity_corner5->create(desktop, item, this,
700                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
701                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
702     entity_corner6->create(desktop, item, this,
703                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
704                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
705     entity_corner7->create(desktop, item, this,
706                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
707                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
708     entity_center->create(desktop, item, this,
709                           _("Move the box in perspective"),
710                           SP_KNOT_SHAPE_CROSS);
712     entity.push_back(entity_corner0);
713     entity.push_back(entity_corner1);
714     entity.push_back(entity_corner2);
715     entity.push_back(entity_corner3);
716     entity.push_back(entity_corner4);
717     entity.push_back(entity_corner5);
718     entity.push_back(entity_corner6);
719     entity.push_back(entity_corner7);
720     entity.push_back(entity_center);
722     add_pattern_knotholder();
725 /* SPArc */
727 class ArcKnotHolderEntityStart : public KnotHolderEntity {
728 public:
729     virtual Geom::Point knot_get();
730     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
731 };
733 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
734 public:
735     virtual Geom::Point knot_get();
736     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
737     virtual void knot_click(guint state);
738 };
740 class ArcKnotHolderEntityRX : public KnotHolderEntity {
741 public:
742     virtual Geom::Point knot_get();
743     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
744     virtual void knot_click(guint state);
745 };
747 class ArcKnotHolderEntityRY : public KnotHolderEntity {
748 public:
749     virtual Geom::Point knot_get();
750     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
751     virtual void knot_click(guint state);
752 };
754 /*
755  * return values:
756  *   1  : inside
757  *   0  : on the curves
758  *   -1 : outside
759  */
760 static gint
761 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
763     gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
764     gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
766     gdouble s = dx * dx + dy * dy;
767     if (s < 1.0) return 1;
768     if (s > 1.0) return -1;
769     return 0;
772 void
773 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
775     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
776     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
778     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
779     SPArc *arc = SP_ARC(item);
781     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
783     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
784     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
785     ge->start = atan2(delta * sc.inverse());
786     if ( ( state & GDK_CONTROL_MASK )
787          && snaps )
788     {
789         ge->start = sp_round(ge->start, M_PI/snaps);
790     }
791     sp_genericellipse_normalize(ge);
792     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
795 Geom::Point
796 ArcKnotHolderEntityStart::knot_get()
798     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
799     SPArc *arc = SP_ARC(item);
801     return sp_arc_get_xy(arc, ge->start);
804 void
805 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
807     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
808     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
810     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
811     SPArc *arc = SP_ARC(item);
813     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
815     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
816     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
817     ge->end = atan2(delta * sc.inverse());
818     if ( ( state & GDK_CONTROL_MASK )
819          && snaps )
820     {
821         ge->end = sp_round(ge->end, M_PI/snaps);
822     }
823     sp_genericellipse_normalize(ge);
824     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
827 Geom::Point
828 ArcKnotHolderEntityEnd::knot_get()
830     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
831     SPArc *arc = SP_ARC(item);
833     return sp_arc_get_xy(arc, ge->end);
837 void
838 ArcKnotHolderEntityEnd::knot_click(guint state)
840     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
842     if (state & GDK_SHIFT_MASK) {
843         ge->end = ge->start = 0;
844         ((SPObject *)ge)->updateRepr();
845     }
849 void
850 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
852     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
854     Geom::Point const s = snap_knot_position(p);
856     ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
858     if ( state & GDK_CONTROL_MASK ) {
859         ge->ry.computed = ge->rx.computed;
860     }
862     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
865 Geom::Point
866 ArcKnotHolderEntityRX::knot_get()
868     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
870     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(ge->rx.computed, 0));
873 void
874 ArcKnotHolderEntityRX::knot_click(guint state)
876     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
878     if (state & GDK_CONTROL_MASK) {
879         ge->ry.computed = ge->rx.computed;
880         ((SPObject *)ge)->updateRepr();
881     }
884 void
885 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
887     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
889     Geom::Point const s = snap_knot_position(p);
891     ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
893     if ( state & GDK_CONTROL_MASK ) {
894         ge->rx.computed = ge->ry.computed;
895     }
897     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
900 Geom::Point
901 ArcKnotHolderEntityRY::knot_get()
903     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
905     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(0, ge->ry.computed));
908 void
909 ArcKnotHolderEntityRY::knot_click(guint state)
911     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
913     if (state & GDK_CONTROL_MASK) {
914         ge->rx.computed = ge->ry.computed;
915         ((SPObject *)ge)->updateRepr();
916     }
919 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
920     KnotHolder(desktop, item, relhandler)
922     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
923     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
924     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
925     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
926     entity_rx->create(desktop, item, this,
927                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
928                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
929     entity_ry->create(desktop, item, this,
930                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
931                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
932     entity_start->create(desktop, item, this,
933                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
934                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
935                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
936     entity_end->create(desktop, item, this,
937                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
938                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
939                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
940     entity.push_back(entity_rx);
941     entity.push_back(entity_ry);
942     entity.push_back(entity_start);
943     entity.push_back(entity_end);
945     add_pattern_knotholder();
948 /* SPStar */
950 class StarKnotHolderEntity1 : public KnotHolderEntity {
951 public:
952     virtual Geom::Point knot_get();
953     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
954     virtual void knot_click(guint state);
955 };
957 class StarKnotHolderEntity2 : public KnotHolderEntity {
958 public:
959     virtual Geom::Point knot_get();
960     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
961     virtual void knot_click(guint state);
962 };
964 void
965 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
967     SPStar *star = SP_STAR(item);
969     Geom::Point const s = snap_knot_position(p);
971     Geom::Point d = s - to_2geom(star->center);
973     double arg1 = atan2(d);
974     double darg1 = arg1 - star->arg[0];
976     if (state & GDK_MOD1_MASK) {
977         star->randomized = darg1/(star->arg[0] - star->arg[1]);
978     } else if (state & GDK_SHIFT_MASK) {
979         star->rounded = darg1/(star->arg[0] - star->arg[1]);
980     } else if (state & GDK_CONTROL_MASK) {
981         star->r[0]    = L2(d);
982     } else {
983         star->r[0]    = L2(d);
984         star->arg[0]  = arg1;
985         star->arg[1] += darg1;
986     }
987     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
990 void
991 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
993     SPStar *star = SP_STAR(item);
995     Geom::Point const s = snap_knot_position(p);
997     if (star->flatsided == false) {
998         Geom::Point d = s - to_2geom(star->center);
1000         double arg1 = atan2(d);
1001         double darg1 = arg1 - star->arg[1];
1003         if (state & GDK_MOD1_MASK) {
1004             star->randomized = darg1/(star->arg[0] - star->arg[1]);
1005         } else if (state & GDK_SHIFT_MASK) {
1006             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1007         } else if (state & GDK_CONTROL_MASK) {
1008             star->r[1]   = L2(d);
1009             star->arg[1] = star->arg[0] + M_PI / star->sides;
1010         }
1011         else {
1012             star->r[1]   = L2(d);
1013             star->arg[1] = atan2(d);
1014         }
1015         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1016     }
1019 Geom::Point
1020 StarKnotHolderEntity1::knot_get()
1022     g_assert(item != NULL);
1024     SPStar *star = SP_STAR(item);
1026     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1030 Geom::Point
1031 StarKnotHolderEntity2::knot_get()
1033     g_assert(item != NULL);
1035     SPStar *star = SP_STAR(item);
1037     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1040 static void
1041 sp_star_knot_click(SPItem *item, guint state)
1043     SPStar *star = SP_STAR(item);
1045     if (state & GDK_MOD1_MASK) {
1046         star->randomized = 0;
1047         ((SPObject *)star)->updateRepr();
1048     } else if (state & GDK_SHIFT_MASK) {
1049         star->rounded = 0;
1050         ((SPObject *)star)->updateRepr();
1051     } else if (state & GDK_CONTROL_MASK) {
1052         star->arg[1] = star->arg[0] + M_PI / star->sides;
1053         ((SPObject *)star)->updateRepr();
1054     }
1057 void
1058 StarKnotHolderEntity1::knot_click(guint state)
1060     return sp_star_knot_click(item, state);
1063 void
1064 StarKnotHolderEntity2::knot_click(guint state)
1066     return sp_star_knot_click(item, state);
1069 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1070     KnotHolder(desktop, item, relhandler)
1072     SPStar *star = SP_STAR(item);
1074     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1075     entity1->create(desktop, item, this,
1076                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1077                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1078     entity.push_back(entity1);
1080     if (star->flatsided == false) {
1081         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1082         entity2->create(desktop, item, this,
1083                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1084                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1085         entity.push_back(entity2);
1086     }
1088     add_pattern_knotholder();
1091 /* SPSpiral */
1093 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1094 public:
1095     virtual Geom::Point knot_get();
1096     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1097     virtual void knot_click(guint state);
1098 };
1100 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1101 public:
1102     virtual Geom::Point knot_get();
1103     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1104 };
1107 /*
1108  * set attributes via inner (t=t0) knot point:
1109  *   [default] increase/decrease inner point
1110  *   [shift]   increase/decrease inner and outer arg synchronizely
1111  *   [control] constrain inner arg to round per PI/4
1112  */
1113 void
1114 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1116     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1117     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1119     SPSpiral *spiral = SP_SPIRAL(item);
1121     gdouble   dx = p[Geom::X] - spiral->cx;
1122     gdouble   dy = p[Geom::Y] - spiral->cy;
1124     if (state & GDK_MOD1_MASK) {
1125         // adjust divergence by vertical drag, relative to rad
1126         double new_exp = (spiral->rad + dy)/(spiral->rad);
1127         spiral->exp = new_exp > 0? new_exp : 0;
1128     } else {
1129         // roll/unroll from inside
1130         gdouble   arg_t0;
1131         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1133         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1134         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1135         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1137         /* round inner arg per PI/snaps, if CTRL is pressed */
1138         if ( ( state & GDK_CONTROL_MASK )
1139              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1140              && ( snaps != 0 ) ) {
1141             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1142             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1143         }
1145         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1146     }
1148     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1151 /*
1152  * set attributes via outer (t=1) knot point:
1153  *   [default] increase/decrease revolution factor
1154  *   [control] constrain inner arg to round per PI/4
1155  */
1156 void
1157 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1159     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1160     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1162     SPSpiral *spiral = SP_SPIRAL(item);
1164     gdouble  dx = p[Geom::X] - spiral->cx;
1165     gdouble  dy = p[Geom::Y] - spiral->cy;
1167     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1168         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1169         if (!(state & GDK_MOD1_MASK)) {
1170             // if alt not pressed, change also rad; otherwise it is locked
1171             spiral->rad = MAX(hypot(dx, dy), 0.001);
1172         }
1173         if ( ( state & GDK_CONTROL_MASK )
1174              && snaps ) {
1175             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1176         }
1177     } else { // roll/unroll
1178         // arg of the spiral outer end
1179         double arg_1;
1180         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1182         // its fractional part after the whole turns are subtracted
1183         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1185         // arg of the mouse point relative to spiral center
1186         double mouse_angle = atan2(dy, dx);
1187         if (mouse_angle < 0)
1188             mouse_angle += 2*M_PI;
1190         // snap if ctrl
1191         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1192             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1193         }
1195         // by how much we want to rotate the outer point
1196         double diff = mouse_angle - arg_r;
1197         if (diff > M_PI)
1198             diff -= 2*M_PI;
1199         else if (diff < -M_PI)
1200             diff += 2*M_PI;
1202         // calculate the new rad;
1203         // the value of t corresponding to the angle arg_1 + diff:
1204         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1205         // the rad at that t:
1206         double rad_new = 0;
1207         if (t_temp > spiral->t0)
1208             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1210         // change the revo (converting diff from radians to the number of turns)
1211         spiral->revo += diff/(2*M_PI);
1212         if (spiral->revo < 1e-3)
1213             spiral->revo = 1e-3;
1215         // if alt not pressed and the values are sane, change the rad
1216         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1217             // adjust t0 too so that the inner point stays unmoved
1218             double r0;
1219             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1220             spiral->rad = rad_new;
1221             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1222         }
1223         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1224         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1225     }
1227     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1230 Geom::Point
1231 SpiralKnotHolderEntityInner::knot_get()
1233     SPSpiral *spiral = SP_SPIRAL(item);
1235     return sp_spiral_get_xy(spiral, spiral->t0);
1238 Geom::Point
1239 SpiralKnotHolderEntityOuter::knot_get()
1241     SPSpiral *spiral = SP_SPIRAL(item);
1243     return sp_spiral_get_xy(spiral, 1.0);
1246 void
1247 SpiralKnotHolderEntityInner::knot_click(guint state)
1249     SPSpiral *spiral = SP_SPIRAL(item);
1251     if (state & GDK_MOD1_MASK) {
1252         spiral->exp = 1;
1253         ((SPObject *)spiral)->updateRepr();
1254     } else if (state & GDK_SHIFT_MASK) {
1255         spiral->t0 = 0;
1256         ((SPObject *)spiral)->updateRepr();
1257     }
1260 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1261     KnotHolder(desktop, item, relhandler)
1263     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1264     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1265     entity_inner->create(desktop, item, this,
1266                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1267                            "with <b>Alt</b> to converge/diverge"));
1268     entity_outer->create(desktop, item, this,
1269                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1270                            "with <b>Shift</b> to scale/rotate"));
1271     entity.push_back(entity_inner);
1272     entity.push_back(entity_outer);
1274     add_pattern_knotholder();
1277 /* SPOffset */
1279 class OffsetKnotHolderEntity : public KnotHolderEntity {
1280 public:
1281     virtual Geom::Point knot_get();
1282     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1283 };
1285 void
1286 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1288     SPOffset *offset = SP_OFFSET(item);
1290     offset->rad = sp_offset_distance_to_original(offset, p);
1291     offset->knot = p;
1292     offset->knotSet = true;
1294     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1298 Geom::Point
1299 OffsetKnotHolderEntity::knot_get()
1301     SPOffset *offset = SP_OFFSET(item);
1303     Geom::Point np;
1304     sp_offset_top_point(offset,&np);
1305     return np;
1308 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1309     KnotHolder(desktop, item, relhandler)
1311     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1312     entity_offset->create(desktop, item, this,
1313                           _("Adjust the <b>offset distance</b>"));
1314     entity.push_back(entity_offset);
1316     add_pattern_knotholder();
1319 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1320 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1321 // sense logically.
1322 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1323 public:
1324     virtual Geom::Point knot_get();
1325     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1326 };
1328 Geom::Point
1329 FlowtextKnotHolderEntity::knot_get()
1331     SPRect *rect = SP_RECT(item);
1333     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1336 void
1337 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1339     set_internal(p, origin, state);
1342 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1343     KnotHolder(desktop, item, relhandler)
1345     g_assert(item != NULL);
1347     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1348     entity_flowtext->create(desktop, item, this,
1349                             _("Drag to resize the <b>flowed text frame</b>"));
1350     entity.push_back(entity_flowtext);
1353 /*
1354   Local Variables:
1355   mode:c++
1356   c-file-style:"stroustrup"
1357   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1358   indent-tabs-mode:nil
1359   fill-column:99
1360   End:
1361 */
1362 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :