Code

Pot and Dutch translation update
[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::SnapConstraint(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     }
177 Geom::Point
178 RectKnotHolderEntityRY::knot_get()
180     SPRect *rect = SP_RECT(item);
182     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
185 void
186 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
188     SPRect *rect = SP_RECT(item);
190     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
191     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
192     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
193     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
195     if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
196                                     // resulting in a perfect circle (and not an ellipse)
197         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
198         rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
199         rect->ry._set = rect->rx._set = true;
200     } else {
201         if (!rect->rx._set || rect->rx.computed == 0) {
202             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
203                                       0.0,
204                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
205         } else {
206             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
207                                       0.0,
208                                       rect->height.computed / 2.0);
209         }
211         rect->ry._set = true;
212     }
214     update_knot();
216     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
219 void
220 RectKnotHolderEntityRY::knot_click(guint state)
222     SPRect *rect = SP_RECT(item);
224     if (state & GDK_SHIFT_MASK) {
225         /* remove rounding */
226         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
227         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
228     } else if (state & GDK_CONTROL_MASK) {
229         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
230         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
231     }
234 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
236 static void sp_rect_clamp_radii(SPRect *rect)
238     // clamp rounding radii so that they do not exceed width/height
239     if (2 * rect->rx.computed > rect->width.computed) {
240         rect->rx.computed = 0.5 * rect->width.computed;
241         rect->rx._set = true;
242     }
243     if (2 * rect->ry.computed > rect->height.computed) {
244         rect->ry.computed = 0.5 * rect->height.computed;
245         rect->ry._set = true;
246     }
249 Geom::Point
250 RectKnotHolderEntityWH::knot_get()
252     SPRect *rect = SP_RECT(item);
254     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
257 void
258 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
260     SPRect *rect = SP_RECT(item);
262     Geom::Point s = p;
264     if (state & GDK_CONTROL_MASK) {
265         // original width/height when drag started
266         gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
267         gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
269         //original ratio
270         gdouble ratio = (w_orig / h_orig);
272         // mouse displacement since drag started
273         gdouble minx = p[Geom::X] - origin[Geom::X];
274         gdouble miny = p[Geom::Y] - origin[Geom::Y];
276         Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
278         if (fabs(minx) > fabs(miny)) {
279             // snap to horizontal or diagonal
280             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
281                 // closer to the diagonal and in same-sign quarters, change both using ratio
282                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
283                 minx = s[Geom::X] - origin[Geom::X];
284                 miny = s[Geom::Y] - origin[Geom::Y];
285                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
286             } else {
287                 // closer to the horizontal, change only width, height is h_orig
288                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
289                 minx = s[Geom::X] - origin[Geom::X];
290                 miny = s[Geom::Y] - origin[Geom::Y];
291                 rect->height.computed = MAX(h_orig, 0);
292             }
293             rect->width.computed = MAX(w_orig + minx, 0);
295         } else {
296             // snap to vertical or diagonal
297             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
298                 // closer to the diagonal and in same-sign quarters, change both using ratio
299                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
300                 minx = s[Geom::X] - origin[Geom::X];
301                 miny = s[Geom::Y] - origin[Geom::Y];
302                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
303             } else {
304                 // closer to the vertical, change only height, width is w_orig
305                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
306                 minx = s[Geom::X] - origin[Geom::X];
307                 miny = s[Geom::Y] - origin[Geom::Y];
308                 rect->width.computed = MAX(w_orig, 0);
309             }
310             rect->height.computed = MAX(h_orig + miny, 0);
312         }
314         rect->width._set = rect->height._set = true;
316     } else {
317         // move freely
318         s = snap_knot_position(p);
319         rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
320         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
321         rect->width._set = rect->height._set = true;
322     }
324     sp_rect_clamp_radii(rect);
326     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
329 void
330 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
332     set_internal(p, origin, state);
333     update_knot();
336 Geom::Point
337 RectKnotHolderEntityXY::knot_get()
339     SPRect *rect = SP_RECT(item);
341     return Geom::Point(rect->x.computed, rect->y.computed);
344 void
345 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
347     SPRect *rect = SP_RECT(item);
349     // opposite corner (unmoved)
350     gdouble opposite_x = (rect->x.computed + rect->width.computed);
351     gdouble opposite_y = (rect->y.computed + rect->height.computed);
353     // original width/height when drag started
354     gdouble w_orig = opposite_x - origin[Geom::X];
355     gdouble h_orig = opposite_y - origin[Geom::Y];
357     Geom::Point s = p;
358     Geom::Point p_handle(rect->x.computed, rect->y.computed);
360     // mouse displacement since drag started
361     gdouble minx = p[Geom::X] - origin[Geom::X];
362     gdouble miny = p[Geom::Y] - origin[Geom::Y];
364     if (state & GDK_CONTROL_MASK) {
365         //original ratio
366         gdouble ratio = (w_orig / h_orig);
368         if (fabs(minx) > fabs(miny)) {
369             // snap to horizontal or diagonal
370             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
371                 // closer to the diagonal and in same-sign quarters, change both using ratio
372                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
373                 minx = s[Geom::X] - origin[Geom::X];
374                 miny = s[Geom::Y] - origin[Geom::Y];
375                 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
376                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
377             } else {
378                 // closer to the horizontal, change only width, height is h_orig
379                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
380                 minx = s[Geom::X] - origin[Geom::X];
381                 miny = s[Geom::Y] - origin[Geom::Y];
382                 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
383                 rect->height.computed = MAX(h_orig, 0);
384             }
385             rect->x.computed = MIN(s[Geom::X], opposite_x);
386             rect->width.computed = MAX(w_orig - minx, 0);
387         } else {
388             // snap to vertical or diagonal
389             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
390                 // closer to the diagonal and in same-sign quarters, change both using ratio
391                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
392                 minx = s[Geom::X] - origin[Geom::X];
393                 miny = s[Geom::Y] - origin[Geom::Y];
394                 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
395                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
396             } else {
397                 // closer to the vertical, change only height, width is w_orig
398                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
399                 minx = s[Geom::X] - origin[Geom::X];
400                 miny = s[Geom::Y] - origin[Geom::Y];
401                 rect->x.computed = MIN(origin[Geom::X], opposite_x);
402                 rect->width.computed = MAX(w_orig, 0);
403             }
404             rect->y.computed = MIN(s[Geom::Y], opposite_y);
405             rect->height.computed = MAX(h_orig - miny, 0);
406         }
408         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
410     } else {
411         // move freely
412         s = snap_knot_position(p);
413         minx = s[Geom::X] - origin[Geom::X];
414         miny = s[Geom::Y] - origin[Geom::Y];
416         rect->x.computed = MIN(s[Geom::X], opposite_x);
417         rect->width.computed = MAX(w_orig - minx, 0);
418         rect->y.computed = MIN(s[Geom::Y], opposite_y);
419         rect->height.computed = MAX(h_orig - miny, 0);
420         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
421     }
423     sp_rect_clamp_radii(rect);
425     update_knot();
427     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
430 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
431     KnotHolder(desktop, item, relhandler)
433     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
434     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
435     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
436     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
437     entity_rx->create(desktop, item, this,
438                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
439                         "to make the vertical radius the same"),
440                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
441     entity_ry->create(desktop, item, this,
442                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
443                         "to make the horizontal radius the same"),
444                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
445     entity_wh->create(desktop, item, this,
446                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
447                         "to lock ratio or stretch in one dimension only"),
448                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
449     entity_xy->create(desktop, item, this,
450                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
451                         "to lock ratio or stretch in one dimension only"),
452                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
453     entity.push_back(entity_rx);
454     entity.push_back(entity_ry);
455     entity.push_back(entity_wh);
456     entity.push_back(entity_xy);
458     add_pattern_knotholder();
461 /* Box3D (= the new 3D box structure) */
463 class Box3DKnotHolderEntity : public KnotHolderEntity {
464 public:
465     virtual Geom::Point knot_get() = 0;
466     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
468     Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
469     void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
470 };
472 Geom::Point
473 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
475     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
478 void
479 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
481     Geom::Point const s = snap_knot_position(new_pos);
483     g_assert(item != NULL);
484     SPBox3D *box = SP_BOX3D(item);
485     Geom::Matrix const i2d (sp_item_i2d_affine (item));
487     Box3D::Axis movement;
488     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
489         movement = Box3D::XY;
490     } else {
491         movement = Box3D::Z;
492     }
494     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
495     box3d_set_z_orders(box);
496     box3d_position_set(box);
499 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
500 public:
501     virtual Geom::Point knot_get();
502     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
503 };
505 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
506 public:
507     virtual Geom::Point knot_get();
508     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
509 };
511 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
512 public:
513     virtual Geom::Point knot_get();
514     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
515 };
517 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
518 public:
519     virtual Geom::Point knot_get();
520     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
521 };
523 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
524 public:
525     virtual Geom::Point knot_get();
526     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
527 };
529 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
530 public:
531     virtual Geom::Point knot_get();
532     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
533 };
535 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
536 public:
537     virtual Geom::Point knot_get();
538     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
539 };
541 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
542 public:
543     virtual Geom::Point knot_get();
544     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
545 };
547 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
548 public:
549     virtual Geom::Point knot_get();
550     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
551 };
553 Geom::Point
554 Box3DKnotHolderEntity0::knot_get()
556     return knot_get_generic(item, 0);
559 Geom::Point
560 Box3DKnotHolderEntity1::knot_get()
562     return knot_get_generic(item, 1);
565 Geom::Point
566 Box3DKnotHolderEntity2::knot_get()
568     return knot_get_generic(item, 2);
571 Geom::Point
572 Box3DKnotHolderEntity3::knot_get()
574     return knot_get_generic(item, 3);
577 Geom::Point
578 Box3DKnotHolderEntity4::knot_get()
580     return knot_get_generic(item, 4);
583 Geom::Point
584 Box3DKnotHolderEntity5::knot_get()
586     return knot_get_generic(item, 5);
589 Geom::Point
590 Box3DKnotHolderEntity6::knot_get()
592     return knot_get_generic(item, 6);
595 Geom::Point
596 Box3DKnotHolderEntity7::knot_get()
598     return knot_get_generic(item, 7);
601 Geom::Point
602 Box3DKnotHolderEntityCenter::knot_get()
604     return box3d_get_center_screen(SP_BOX3D(item));
607 void
608 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
610     knot_set_generic(item, 0, new_pos, state);
613 void
614 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
616     knot_set_generic(item, 1, new_pos, state);
619 void
620 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
622     knot_set_generic(item, 2, new_pos, state);
625 void
626 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
628     knot_set_generic(item, 3, new_pos, state);
631 void
632 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
634     knot_set_generic(item, 4, new_pos, state);
637 void
638 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
640     knot_set_generic(item, 5, new_pos, state);
643 void
644 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
646     knot_set_generic(item, 6, new_pos, state);
649 void
650 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
652     knot_set_generic(item, 7, new_pos, state);
655 void
656 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
658     Geom::Point const s = snap_knot_position(new_pos);
660     SPBox3D *box = SP_BOX3D(item);
661     Geom::Matrix const i2d (sp_item_i2d_affine (item));
663     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
664                       state & GDK_CONTROL_MASK);
666     box3d_set_z_orders(box);
667     box3d_position_set(box);
670 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
671     KnotHolder(desktop, item, relhandler)
673     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
674     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
675     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
676     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
677     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
678     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
679     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
680     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
681     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
683     entity_corner0->create(desktop, item, this,
684                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
685                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
686     entity_corner1->create(desktop, item, this,
687                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
688                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
689     entity_corner2->create(desktop, item, this,
690                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
691                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
692     entity_corner3->create(desktop, item, this,
693                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
694                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
695     entity_corner4->create(desktop, item, this,
696                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
697                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
698     entity_corner5->create(desktop, item, this,
699                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
700                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
701     entity_corner6->create(desktop, item, this,
702                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
703                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
704     entity_corner7->create(desktop, item, this,
705                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
706                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
707     entity_center->create(desktop, item, this,
708                           _("Move the box in perspective"),
709                           SP_KNOT_SHAPE_CROSS);
711     entity.push_back(entity_corner0);
712     entity.push_back(entity_corner1);
713     entity.push_back(entity_corner2);
714     entity.push_back(entity_corner3);
715     entity.push_back(entity_corner4);
716     entity.push_back(entity_corner5);
717     entity.push_back(entity_corner6);
718     entity.push_back(entity_corner7);
719     entity.push_back(entity_center);
721     add_pattern_knotholder();
724 /* SPArc */
726 class ArcKnotHolderEntityStart : public KnotHolderEntity {
727 public:
728     virtual Geom::Point knot_get();
729     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
730 };
732 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
733 public:
734     virtual Geom::Point knot_get();
735     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
736     virtual void knot_click(guint state);
737 };
739 class ArcKnotHolderEntityRX : public KnotHolderEntity {
740 public:
741     virtual Geom::Point knot_get();
742     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
743     virtual void knot_click(guint state);
744 };
746 class ArcKnotHolderEntityRY : public KnotHolderEntity {
747 public:
748     virtual Geom::Point knot_get();
749     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
750     virtual void knot_click(guint state);
751 };
753 /*
754  * return values:
755  *   1  : inside
756  *   0  : on the curves
757  *   -1 : outside
758  */
759 static gint
760 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
762     gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
763     gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
765     gdouble s = dx * dx + dy * dy;
766     if (s < 1.0) return 1;
767     if (s > 1.0) return -1;
768     return 0;
771 void
772 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
774     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
775     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
777     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
778     SPArc *arc = SP_ARC(item);
780     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
782     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
783     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
784     ge->start = atan2(delta * sc.inverse());
785     if ( ( state & GDK_CONTROL_MASK )
786          && snaps )
787     {
788         ge->start = sp_round(ge->start, M_PI/snaps);
789     }
790     sp_genericellipse_normalize(ge);
791     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
794 Geom::Point
795 ArcKnotHolderEntityStart::knot_get()
797     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
798     SPArc *arc = SP_ARC(item);
800     return sp_arc_get_xy(arc, ge->start);
803 void
804 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
806     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
807     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
809     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
810     SPArc *arc = SP_ARC(item);
812     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
814     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
815     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
816     ge->end = atan2(delta * sc.inverse());
817     if ( ( state & GDK_CONTROL_MASK )
818          && snaps )
819     {
820         ge->end = sp_round(ge->end, M_PI/snaps);
821     }
822     sp_genericellipse_normalize(ge);
823     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
826 Geom::Point
827 ArcKnotHolderEntityEnd::knot_get()
829     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
830     SPArc *arc = SP_ARC(item);
832     return sp_arc_get_xy(arc, ge->end);
836 void
837 ArcKnotHolderEntityEnd::knot_click(guint state)
839     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
841     if (state & GDK_SHIFT_MASK) {
842         ge->end = ge->start = 0;
843         ((SPObject *)ge)->updateRepr();
844     }
848 void
849 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
851     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
853     Geom::Point const s = snap_knot_position(p);
855     ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
857     if ( state & GDK_CONTROL_MASK ) {
858         ge->ry.computed = ge->rx.computed;
859     }
861     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
864 Geom::Point
865 ArcKnotHolderEntityRX::knot_get()
867     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
869     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(ge->rx.computed, 0));
872 void
873 ArcKnotHolderEntityRX::knot_click(guint state)
875     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
877     if (state & GDK_CONTROL_MASK) {
878         ge->ry.computed = ge->rx.computed;
879         ((SPObject *)ge)->updateRepr();
880     }
883 void
884 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
886     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
888     Geom::Point const s = snap_knot_position(p);
890     ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
892     if ( state & GDK_CONTROL_MASK ) {
893         ge->rx.computed = ge->ry.computed;
894     }
896     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
899 Geom::Point
900 ArcKnotHolderEntityRY::knot_get()
902     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
904     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(0, ge->ry.computed));
907 void
908 ArcKnotHolderEntityRY::knot_click(guint state)
910     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
912     if (state & GDK_CONTROL_MASK) {
913         ge->rx.computed = ge->ry.computed;
914         ((SPObject *)ge)->updateRepr();
915     }
918 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
919     KnotHolder(desktop, item, relhandler)
921     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
922     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
923     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
924     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
925     entity_rx->create(desktop, item, this,
926                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
927                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
928     entity_ry->create(desktop, item, this,
929                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
930                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
931     entity_start->create(desktop, item, this,
932                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> "
933                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
934                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
935     entity_end->create(desktop, item, this,
936                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
937                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
938                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
939     entity.push_back(entity_rx);
940     entity.push_back(entity_ry);
941     entity.push_back(entity_start);
942     entity.push_back(entity_end);
944     add_pattern_knotholder();
947 /* SPStar */
949 class StarKnotHolderEntity1 : public KnotHolderEntity {
950 public:
951     virtual Geom::Point knot_get();
952     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
953     virtual void knot_click(guint state);
954 };
956 class StarKnotHolderEntity2 : public KnotHolderEntity {
957 public:
958     virtual Geom::Point knot_get();
959     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
960     virtual void knot_click(guint state);
961 };
963 void
964 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
966     SPStar *star = SP_STAR(item);
968     Geom::Point const s = snap_knot_position(p);
970     Geom::Point d = s - to_2geom(star->center);
972     double arg1 = atan2(d);
973     double darg1 = arg1 - star->arg[0];
975     if (state & GDK_MOD1_MASK) {
976         star->randomized = darg1/(star->arg[0] - star->arg[1]);
977     } else if (state & GDK_SHIFT_MASK) {
978         star->rounded = darg1/(star->arg[0] - star->arg[1]);
979     } else if (state & GDK_CONTROL_MASK) {
980         star->r[0]    = L2(d);
981     } else {
982         star->r[0]    = L2(d);
983         star->arg[0]  = arg1;
984         star->arg[1] += darg1;
985     }
986     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
989 void
990 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
992     SPStar *star = SP_STAR(item);
994     Geom::Point const s = snap_knot_position(p);
996     if (star->flatsided == false) {
997         Geom::Point d = s - to_2geom(star->center);
999         double arg1 = atan2(d);
1000         double darg1 = arg1 - star->arg[1];
1002         if (state & GDK_MOD1_MASK) {
1003             star->randomized = darg1/(star->arg[0] - star->arg[1]);
1004         } else if (state & GDK_SHIFT_MASK) {
1005             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1006         } else if (state & GDK_CONTROL_MASK) {
1007             star->r[1]   = L2(d);
1008             star->arg[1] = star->arg[0] + M_PI / star->sides;
1009         }
1010         else {
1011             star->r[1]   = L2(d);
1012             star->arg[1] = atan2(d);
1013         }
1014         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1015     }
1018 Geom::Point
1019 StarKnotHolderEntity1::knot_get()
1021     g_assert(item != NULL);
1023     SPStar *star = SP_STAR(item);
1025     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1029 Geom::Point
1030 StarKnotHolderEntity2::knot_get()
1032     g_assert(item != NULL);
1034     SPStar *star = SP_STAR(item);
1036     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1039 static void
1040 sp_star_knot_click(SPItem *item, guint state)
1042     SPStar *star = SP_STAR(item);
1044     if (state & GDK_MOD1_MASK) {
1045         star->randomized = 0;
1046         ((SPObject *)star)->updateRepr();
1047     } else if (state & GDK_SHIFT_MASK) {
1048         star->rounded = 0;
1049         ((SPObject *)star)->updateRepr();
1050     } else if (state & GDK_CONTROL_MASK) {
1051         star->arg[1] = star->arg[0] + M_PI / star->sides;
1052         ((SPObject *)star)->updateRepr();
1053     }
1056 void
1057 StarKnotHolderEntity1::knot_click(guint state)
1059     return sp_star_knot_click(item, state);
1062 void
1063 StarKnotHolderEntity2::knot_click(guint state)
1065     return sp_star_knot_click(item, state);
1068 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1069     KnotHolder(desktop, item, relhandler)
1071     SPStar *star = SP_STAR(item);
1073     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1074     entity1->create(desktop, item, this,
1075                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1076                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1077     entity.push_back(entity1);
1079     if (star->flatsided == false) {
1080         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1081         entity2->create(desktop, item, this,
1082                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1083                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1084         entity.push_back(entity2);
1085     }
1087     add_pattern_knotholder();
1090 /* SPSpiral */
1092 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1093 public:
1094     virtual Geom::Point knot_get();
1095     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1096     virtual void knot_click(guint state);
1097 };
1099 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1100 public:
1101     virtual Geom::Point knot_get();
1102     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1103 };
1106 /*
1107  * set attributes via inner (t=t0) knot point:
1108  *   [default] increase/decrease inner point
1109  *   [shift]   increase/decrease inner and outer arg synchronizely
1110  *   [control] constrain inner arg to round per PI/4
1111  */
1112 void
1113 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1115     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1116     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1118     SPSpiral *spiral = SP_SPIRAL(item);
1120     gdouble   dx = p[Geom::X] - spiral->cx;
1121     gdouble   dy = p[Geom::Y] - spiral->cy;
1123     gdouble   moved_y = p[Geom::Y] - origin[Geom::Y];
1125     if (state & GDK_MOD1_MASK) {
1126         // adjust divergence by vertical drag, relative to rad
1127         if (spiral->rad > 0) {
1128             double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down
1129             spiral->exp += exp_delta;
1130             if (spiral->exp < 1e-3)
1131                 spiral->exp = 1e-3;
1132         }
1133     } else {
1134         // roll/unroll from inside
1135         gdouble   arg_t0;
1136         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1138         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1139         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1140         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1142         /* round inner arg per PI/snaps, if CTRL is pressed */
1143         if ( ( state & GDK_CONTROL_MASK )
1144              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1145              && ( snaps != 0 ) ) {
1146             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1147             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1148         }
1150         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1151     }
1153     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1156 /*
1157  * set attributes via outer (t=1) knot point:
1158  *   [default] increase/decrease revolution factor
1159  *   [control] constrain inner arg to round per PI/4
1160  */
1161 void
1162 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1164     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1165     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1167     SPSpiral *spiral = SP_SPIRAL(item);
1169     gdouble  dx = p[Geom::X] - spiral->cx;
1170     gdouble  dy = p[Geom::Y] - spiral->cy;
1172     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1173         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1174         if (!(state & GDK_MOD1_MASK)) {
1175             // if alt not pressed, change also rad; otherwise it is locked
1176             spiral->rad = MAX(hypot(dx, dy), 0.001);
1177         }
1178         if ( ( state & GDK_CONTROL_MASK )
1179              && snaps ) {
1180             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1181         }
1182     } else { // roll/unroll
1183         // arg of the spiral outer end
1184         double arg_1;
1185         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1187         // its fractional part after the whole turns are subtracted
1188         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1190         // arg of the mouse point relative to spiral center
1191         double mouse_angle = atan2(dy, dx);
1192         if (mouse_angle < 0)
1193             mouse_angle += 2*M_PI;
1195         // snap if ctrl
1196         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1197             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1198         }
1200         // by how much we want to rotate the outer point
1201         double diff = mouse_angle - arg_r;
1202         if (diff > M_PI)
1203             diff -= 2*M_PI;
1204         else if (diff < -M_PI)
1205             diff += 2*M_PI;
1207         // calculate the new rad;
1208         // the value of t corresponding to the angle arg_1 + diff:
1209         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1210         // the rad at that t:
1211         double rad_new = 0;
1212         if (t_temp > spiral->t0)
1213             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1215         // change the revo (converting diff from radians to the number of turns)
1216         spiral->revo += diff/(2*M_PI);
1217         if (spiral->revo < 1e-3)
1218             spiral->revo = 1e-3;
1220         // if alt not pressed and the values are sane, change the rad
1221         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1222             // adjust t0 too so that the inner point stays unmoved
1223             double r0;
1224             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1225             spiral->rad = rad_new;
1226             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1227         }
1228         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1229         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1230     }
1232     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1235 Geom::Point
1236 SpiralKnotHolderEntityInner::knot_get()
1238     SPSpiral *spiral = SP_SPIRAL(item);
1240     return sp_spiral_get_xy(spiral, spiral->t0);
1243 Geom::Point
1244 SpiralKnotHolderEntityOuter::knot_get()
1246     SPSpiral *spiral = SP_SPIRAL(item);
1248     return sp_spiral_get_xy(spiral, 1.0);
1251 void
1252 SpiralKnotHolderEntityInner::knot_click(guint state)
1254     SPSpiral *spiral = SP_SPIRAL(item);
1256     if (state & GDK_MOD1_MASK) {
1257         spiral->exp = 1;
1258         ((SPObject *)spiral)->updateRepr();
1259     } else if (state & GDK_SHIFT_MASK) {
1260         spiral->t0 = 0;
1261         ((SPObject *)spiral)->updateRepr();
1262     }
1265 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1266     KnotHolder(desktop, item, relhandler)
1268     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1269     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1270     entity_inner->create(desktop, item, this,
1271                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1272                            "with <b>Alt</b> to converge/diverge"));
1273     entity_outer->create(desktop, item, this,
1274                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1275                            "with <b>Shift</b> to scale/rotate"));
1276     entity.push_back(entity_inner);
1277     entity.push_back(entity_outer);
1279     add_pattern_knotholder();
1282 /* SPOffset */
1284 class OffsetKnotHolderEntity : public KnotHolderEntity {
1285 public:
1286     virtual Geom::Point knot_get();
1287     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1288 };
1290 void
1291 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1293     SPOffset *offset = SP_OFFSET(item);
1295     offset->rad = sp_offset_distance_to_original(offset, p);
1296     offset->knot = p;
1297     offset->knotSet = true;
1299     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1303 Geom::Point
1304 OffsetKnotHolderEntity::knot_get()
1306     SPOffset *offset = SP_OFFSET(item);
1308     Geom::Point np;
1309     sp_offset_top_point(offset,&np);
1310     return np;
1313 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1314     KnotHolder(desktop, item, relhandler)
1316     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1317     entity_offset->create(desktop, item, this,
1318                           _("Adjust the <b>offset distance</b>"));
1319     entity.push_back(entity_offset);
1321     add_pattern_knotholder();
1324 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1325 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1326 // sense logically.
1327 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1328 public:
1329     virtual Geom::Point knot_get();
1330     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1331 };
1333 Geom::Point
1334 FlowtextKnotHolderEntity::knot_get()
1336     SPRect *rect = SP_RECT(item);
1338     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1341 void
1342 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1344     set_internal(p, origin, state);
1347 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1348     KnotHolder(desktop, item, relhandler)
1350     g_assert(item != NULL);
1352     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1353     entity_flowtext->create(desktop, item, this,
1354                             _("Drag to resize the <b>flowed text frame</b>"));
1355     entity.push_back(entity_flowtext);
1358 /*
1359   Local Variables:
1360   mode:c++
1361   c-file-style:"stroustrup"
1362   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1363   indent-tabs-mode:nil
1364   fill-column:99
1365   End:
1366 */
1367 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :