Code

More NR ==> Geom changes
[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 "desktop-affine.h"
30 #include "style.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "sp-namedview.h"
34 #include "live_effects/effect.h"
36 #include "sp-pattern.h"
37 #include "sp-path.h"
39 #include <glibmm/i18n.h>
41 #include "object-edit.h"
43 #include <libnr/nr-scale-ops.h>
45 #include "xml/repr.h"
47 #include "2geom/isnan.h"
49 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
51 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
52 {
53     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
55     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
56     effect->addHandles(knot_holder, desktop, item);
58     return knot_holder;
59 }
61 KnotHolder *
62 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
63 {
64     KnotHolder *knotholder = NULL;
66     if (SP_IS_LPE_ITEM(item) &&
67         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
68         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
69         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
70         knotholder = sp_lpe_knot_holder(item, desktop);
71     } else if (SP_IS_RECT(item)) {
72         knotholder = new RectKnotHolder(desktop, item, NULL);
73     } else if (SP_IS_BOX3D(item)) {
74         knotholder = new Box3DKnotHolder(desktop, item, NULL);
75     } else if (SP_IS_ARC(item)) {
76         knotholder = new ArcKnotHolder(desktop, item, NULL);
77     } else if (SP_IS_STAR(item)) {
78         knotholder = new StarKnotHolder(desktop, item, NULL);
79     } else if (SP_IS_SPIRAL(item)) {
80         knotholder = new SpiralKnotHolder(desktop, item, NULL);
81     } else if (SP_IS_OFFSET(item)) {
82         knotholder = new OffsetKnotHolder(desktop, item, NULL);
83     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
84         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
85     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
86                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
87         knotholder = new KnotHolder(desktop, item, NULL);
88         knotholder->add_pattern_knotholder();
89     }
91     return knotholder;
92 }
94 /* SPRect */
96 /* handle for horizontal rounding radius */
97 class RectKnotHolderEntityRX : public KnotHolderEntity {
98 public:
99     virtual Geom::Point knot_get();
100     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
101     virtual void knot_click(guint state);
102 };
104 /* handle for vertical rounding radius */
105 class RectKnotHolderEntityRY : public KnotHolderEntity {
106 public:
107     virtual Geom::Point knot_get();
108     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
109     virtual void knot_click(guint state);
110 };
112 /* handle for width/height adjustment */
113 class RectKnotHolderEntityWH : public KnotHolderEntity {
114 public:
115     virtual Geom::Point knot_get();
116     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
118 protected:
119     void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
120 };
122 /* handle for x/y adjustment */
123 class RectKnotHolderEntityXY : public KnotHolderEntity {
124 public:
125     virtual Geom::Point knot_get();
126     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
127 };
129 Geom::Point
130 RectKnotHolderEntityRX::knot_get()
132     SPRect *rect = SP_RECT(item);
134     return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
137 void
138 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
140     SPRect *rect = SP_RECT(item);
142     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
143     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
144     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
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 - p[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 - p[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
195     if (state & GDK_CONTROL_MASK) {
196         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
197         rect->rx.computed = rect->ry.computed = CLAMP(p[Geom::Y] - rect->y.computed, 0.0, temp);
198         rect->ry._set = rect->rx._set = true;
199     } else {
200         if (!rect->rx._set || rect->rx.computed == 0) {
201             rect->ry.computed = CLAMP(p[Geom::Y] - rect->y.computed,
202                                       0.0,
203                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
204         } else {
205             rect->ry.computed = CLAMP(p[Geom::Y] - rect->y.computed,
206                                       0.0,
207                                       rect->height.computed / 2.0);
208         }
210         rect->ry._set = true;
211     }
213     update_knot();
215     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
218 void
219 RectKnotHolderEntityRY::knot_click(guint state)
221     SPRect *rect = SP_RECT(item);
223     if (state & GDK_SHIFT_MASK) {
224         /* remove rounding */
225         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
226         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
227     } else if (state & GDK_CONTROL_MASK) {
228         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
229         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
230     }
233 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
235 static void sp_rect_clamp_radii(SPRect *rect)
237     // clamp rounding radii so that they do not exceed width/height
238     if (2 * rect->rx.computed > rect->width.computed) {
239         rect->rx.computed = 0.5 * rect->width.computed;
240         rect->rx._set = true;
241     }
242     if (2 * rect->ry.computed > rect->height.computed) {
243         rect->ry.computed = 0.5 * rect->height.computed;
244         rect->ry._set = true;
245     }
248 Geom::Point
249 RectKnotHolderEntityWH::knot_get()
251     SPRect *rect = SP_RECT(item);
253     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
256 void
257 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
259     SPRect *rect = SP_RECT(item);
260     Geom::Point const s = snap_knot_position(p);
262     if (state & GDK_CONTROL_MASK) {
263         // original width/height when drag started
264         gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
265         gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
267         //original ratio
268         gdouble const ratio = (w_orig / h_orig);
270         // mouse displacement since drag started
271         gdouble const minx = s[Geom::X] - origin[Geom::X];
272         gdouble const miny = s[Geom::Y] - origin[Geom::Y];
274         if (fabs(minx) > fabs(miny)) {
276             // snap to horizontal or diagonal
277             rect->width.computed = MAX(w_orig + minx, 0);
278             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
279                 // closer to the diagonal and in same-sign quarters, change both using ratio
280                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
281             } else {
282                 // closer to the horizontal, change only width, height is h_orig
283                 rect->height.computed = MAX(h_orig, 0);
284             }
286         } else {
287             // snap to vertical or diagonal
288             rect->height.computed = MAX(h_orig + miny, 0);
289             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
290                 // closer to the diagonal and in same-sign quarters, change both using ratio
291                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
292             } else {
293                 // closer to the vertical, change only height, width is w_orig
294                 rect->width.computed = MAX(w_orig, 0);
295             }
296         }
298         rect->width._set = rect->height._set = true;
300     } else {
301         // move freely
302         rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
303         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
304         rect->width._set = rect->height._set = true;
305     }
307     sp_rect_clamp_radii(rect);
309     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
312 void
313 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
315     set_internal(p, origin, state);
316     update_knot();
319 Geom::Point
320 RectKnotHolderEntityXY::knot_get()
322     SPRect *rect = SP_RECT(item);
324     return Geom::Point(rect->x.computed, rect->y.computed);
327 void
328 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
330     SPRect *rect = SP_RECT(item);
332     // opposite corner (unmoved)
333     gdouble opposite_x = (rect->x.computed + rect->width.computed);
334     gdouble opposite_y = (rect->y.computed + rect->height.computed);
336     // original width/height when drag started
337     gdouble w_orig = opposite_x - origin[Geom::X];
338     gdouble h_orig = opposite_y - origin[Geom::Y];
340     Geom::Point const s = snap_knot_position(p);
342     // mouse displacement since drag started
343     gdouble minx = s[Geom::X] - origin[Geom::X];
344     gdouble miny = s[Geom::Y] - origin[Geom::Y];
346     if (state & GDK_CONTROL_MASK) {
347         //original ratio
348         gdouble ratio = (w_orig / h_orig);
350         if (fabs(minx) > fabs(miny)) {
352             // snap to horizontal or diagonal
353             rect->x.computed = MIN(s[Geom::X], opposite_x);
354             rect->width.computed = MAX(w_orig - minx, 0);
355             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
356                 // closer to the diagonal and in same-sign quarters, change both using ratio
357                 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
358                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
359             } else {
360                 // closer to the horizontal, change only width, height is h_orig
361                 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
362                 rect->height.computed = MAX(h_orig, 0);
363             }
365         } else {
367             // snap to vertical or diagonal
368             rect->y.computed = MIN(s[Geom::Y], opposite_y);
369             rect->height.computed = MAX(h_orig - miny, 0);
370             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
371                 // closer to the diagonal and in same-sign quarters, change both using ratio
372                 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
373                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
374             } else {
375                 // closer to the vertical, change only height, width is w_orig
376                 rect->x.computed = MIN(origin[Geom::X], opposite_x);
377                 rect->width.computed = MAX(w_orig, 0);
378             }
380         }
382         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
384     } else {
385         // move freely
386         rect->x.computed = MIN(s[Geom::X], opposite_x);
387         rect->width.computed = MAX(w_orig - minx, 0);
388         rect->y.computed = MIN(s[Geom::Y], opposite_y);
389         rect->height.computed = MAX(h_orig - miny, 0);
390         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
391     }
393     sp_rect_clamp_radii(rect);
395     update_knot();
397     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
400 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
401     KnotHolder(desktop, item, relhandler)
403     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
404     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
405     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
406     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
407     entity_rx->create(desktop, item, this,
408                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
409                         "to make the vertical radius the same"),
410                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
411     entity_ry->create(desktop, item, this,
412                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
413                         "to make the horizontal radius the same"),
414                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
415     entity_wh->create(desktop, item, this,
416                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
417                         "to lock ratio or stretch in one dimension only"),
418                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
419     entity_xy->create(desktop, item, this,
420                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
421                         "to lock ratio or stretch in one dimension only"),
422                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
423     entity.push_back(entity_rx);
424     entity.push_back(entity_ry);
425     entity.push_back(entity_wh);
426     entity.push_back(entity_xy);
428     add_pattern_knotholder();
431 /* Box3D (= the new 3D box structure) */
433 class Box3DKnotHolderEntity : public KnotHolderEntity {
434 public:
435     virtual Geom::Point knot_get() = 0;
436     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
438     Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
439     void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
440 };
442 Geom::Point
443 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
445     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
448 void
449 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
451     Geom::Point const s = snap_knot_position(new_pos);
453     g_assert(item != NULL);
454     SPBox3D *box = SP_BOX3D(item);
455     Geom::Matrix const i2d (sp_item_i2d_affine (item));
457     Box3D::Axis movement;
458     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
459         movement = Box3D::XY;
460     } else {
461         movement = Box3D::Z;
462     }
464     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
465     box3d_set_z_orders(box);
466     box3d_position_set(box);
469 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
470 public:
471     virtual Geom::Point knot_get();
472     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
473 };
475 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
476 public:
477     virtual Geom::Point knot_get();
478     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
479 };
481 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
482 public:
483     virtual Geom::Point knot_get();
484     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
485 };
487 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
488 public:
489     virtual Geom::Point knot_get();
490     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
491 };
493 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
494 public:
495     virtual Geom::Point knot_get();
496     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
497 };
499 class Box3DKnotHolderEntity5 : 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 Box3DKnotHolderEntity6 : 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 Box3DKnotHolderEntity7 : 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 Box3DKnotHolderEntityCenter : public KnotHolderEntity {
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 Geom::Point
524 Box3DKnotHolderEntity0::knot_get()
526     return knot_get_generic(item, 0);
529 Geom::Point
530 Box3DKnotHolderEntity1::knot_get()
532     return knot_get_generic(item, 1);
535 Geom::Point
536 Box3DKnotHolderEntity2::knot_get()
538     return knot_get_generic(item, 2);
541 Geom::Point
542 Box3DKnotHolderEntity3::knot_get()
544     return knot_get_generic(item, 3);
547 Geom::Point
548 Box3DKnotHolderEntity4::knot_get()
550     return knot_get_generic(item, 4);
553 Geom::Point
554 Box3DKnotHolderEntity5::knot_get()
556     return knot_get_generic(item, 5);
559 Geom::Point
560 Box3DKnotHolderEntity6::knot_get()
562     return knot_get_generic(item, 6);
565 Geom::Point
566 Box3DKnotHolderEntity7::knot_get()
568     return knot_get_generic(item, 7);
571 Geom::Point
572 Box3DKnotHolderEntityCenter::knot_get()
574     return box3d_get_center_screen(SP_BOX3D(item));
577 void
578 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
580     knot_set_generic(item, 0, new_pos, state);
583 void
584 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
586     knot_set_generic(item, 1, new_pos, state);
589 void
590 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
592     knot_set_generic(item, 2, new_pos, state);
595 void
596 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
598     knot_set_generic(item, 3, new_pos, state);
601 void
602 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
604     knot_set_generic(item, 4, new_pos, state);
607 void
608 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
610     knot_set_generic(item, 5, new_pos, state);
613 void
614 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
616     knot_set_generic(item, 6, new_pos, state);
619 void
620 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
622     knot_set_generic(item, 7, new_pos, state);
625 void
626 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
628     Geom::Point const s = snap_knot_position(new_pos);
630     SPBox3D *box = SP_BOX3D(item);
631     Geom::Matrix const i2d (sp_item_i2d_affine (item));
633     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
634                       state & GDK_CONTROL_MASK);
636     box3d_set_z_orders(box);
637     box3d_position_set(box);
640 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
641     KnotHolder(desktop, item, relhandler)
643     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
644     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
645     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
646     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
647     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
648     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
649     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
650     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
651     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
653     entity_corner0->create(desktop, item, this,
654                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
655                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
656     entity_corner1->create(desktop, item, this,
657                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
658                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
659     entity_corner2->create(desktop, item, this,
660                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
661                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
662     entity_corner3->create(desktop, item, this,
663                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
664                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
665     entity_corner4->create(desktop, item, this,
666                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
667                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
668     entity_corner5->create(desktop, item, this,
669                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
670                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
671     entity_corner6->create(desktop, item, this,
672                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
673                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
674     entity_corner7->create(desktop, item, this,
675                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
676                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
677     entity_center->create(desktop, item, this,
678                           _("Move the box in perspective"),
679                           SP_KNOT_SHAPE_CROSS);
681     entity.push_back(entity_corner0);
682     entity.push_back(entity_corner1);
683     entity.push_back(entity_corner2);
684     entity.push_back(entity_corner3);
685     entity.push_back(entity_corner4);
686     entity.push_back(entity_corner5);
687     entity.push_back(entity_corner6);
688     entity.push_back(entity_corner7);
689     entity.push_back(entity_center);
691     add_pattern_knotholder();
694 /* SPArc */
696 class ArcKnotHolderEntityStart : public KnotHolderEntity {
697 public:
698     virtual Geom::Point knot_get();
699     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
700 };
702 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
703 public:
704     virtual Geom::Point knot_get();
705     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
706     virtual void knot_click(guint state);
707 };
709 class ArcKnotHolderEntityRX : public KnotHolderEntity {
710 public:
711     virtual Geom::Point knot_get();
712     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
713     virtual void knot_click(guint state);
714 };
716 class ArcKnotHolderEntityRY : public KnotHolderEntity {
717 public:
718     virtual Geom::Point knot_get();
719     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
720     virtual void knot_click(guint state);
721 };
723 /*
724  * return values:
725  *   1  : inside
726  *   0  : on the curves
727  *   -1 : outside
728  */
729 static gint
730 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
732     gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
733     gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
735     gdouble s = dx * dx + dy * dy;
736     if (s < 1.0) return 1;
737     if (s > 1.0) return -1;
738     return 0;
741 void
742 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
744     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
745     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
747     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
748     SPArc *arc = SP_ARC(item);
750     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
752     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
753     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
754     ge->start = atan2(delta * sc.inverse());
755     if ( ( state & GDK_CONTROL_MASK )
756          && snaps )
757     {
758         ge->start = sp_round(ge->start, M_PI/snaps);
759     }
760     sp_genericellipse_normalize(ge);
761     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
764 Geom::Point
765 ArcKnotHolderEntityStart::knot_get()
767     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
768     SPArc *arc = SP_ARC(item);
770     return sp_arc_get_xy(arc, ge->start);
773 void
774 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
776     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
777     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
779     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
780     SPArc *arc = SP_ARC(item);
782     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
784     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
785     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
786     ge->end = atan2(delta * sc.inverse());
787     if ( ( state & GDK_CONTROL_MASK )
788          && snaps )
789     {
790         ge->end = sp_round(ge->end, M_PI/snaps);
791     }
792     sp_genericellipse_normalize(ge);
793     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
796 Geom::Point
797 ArcKnotHolderEntityEnd::knot_get()
799     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
800     SPArc *arc = SP_ARC(item);
802     return sp_arc_get_xy(arc, ge->end);
806 void
807 ArcKnotHolderEntityEnd::knot_click(guint state)
809     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
811     if (state & GDK_SHIFT_MASK) {
812         ge->end = ge->start = 0;
813         ((SPObject *)ge)->updateRepr();
814     }
818 void
819 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
821     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
823     Geom::Point const s = snap_knot_position(p);
825     ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
827     if ( state & GDK_CONTROL_MASK ) {
828         ge->ry.computed = ge->rx.computed;
829     }
831     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
834 Geom::Point
835 ArcKnotHolderEntityRX::knot_get()
837     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
839     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(ge->rx.computed, 0));
842 void
843 ArcKnotHolderEntityRX::knot_click(guint state)
845     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
847     if (state & GDK_CONTROL_MASK) {
848         ge->ry.computed = ge->rx.computed;
849         ((SPObject *)ge)->updateRepr();
850     }
853 void
854 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
856     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
858     Geom::Point const s = snap_knot_position(p);
860     ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
862     if ( state & GDK_CONTROL_MASK ) {
863         ge->rx.computed = ge->ry.computed;
864     }
866     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
869 Geom::Point
870 ArcKnotHolderEntityRY::knot_get()
872     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
874     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(0, ge->ry.computed));
877 void
878 ArcKnotHolderEntityRY::knot_click(guint state)
880     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
882     if (state & GDK_CONTROL_MASK) {
883         ge->rx.computed = ge->ry.computed;
884         ((SPObject *)ge)->updateRepr();
885     }
888 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
889     KnotHolder(desktop, item, relhandler)
891     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
892     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
893     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
894     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
895     entity_rx->create(desktop, item, this,
896                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
897                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
898     entity_ry->create(desktop, item, this,
899                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
900                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
901     entity_start->create(desktop, item, this,
902                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
903                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
904                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
905     entity_end->create(desktop, item, this,
906                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
907                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
908                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
909     entity.push_back(entity_rx);
910     entity.push_back(entity_ry);
911     entity.push_back(entity_start);
912     entity.push_back(entity_end);
914     add_pattern_knotholder();
917 /* SPStar */
919 class StarKnotHolderEntity1 : public KnotHolderEntity {
920 public:
921     virtual Geom::Point knot_get();
922     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
923     virtual void knot_click(guint state);
924 };
926 class StarKnotHolderEntity2 : public KnotHolderEntity {
927 public:
928     virtual Geom::Point knot_get();
929     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
930     virtual void knot_click(guint state);
931 };
933 void
934 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
936     SPStar *star = SP_STAR(item);
938     Geom::Point const s = snap_knot_position(p);
940     Geom::Point d = s - to_2geom(star->center);
942     double arg1 = atan2(d);
943     double darg1 = arg1 - star->arg[0];
945     if (state & GDK_MOD1_MASK) {
946         star->randomized = darg1/(star->arg[0] - star->arg[1]);
947     } else if (state & GDK_SHIFT_MASK) {
948         star->rounded = darg1/(star->arg[0] - star->arg[1]);
949     } else if (state & GDK_CONTROL_MASK) {
950         star->r[0]    = L2(d);
951     } else {
952         star->r[0]    = L2(d);
953         star->arg[0]  = arg1;
954         star->arg[1] += darg1;
955     }
956     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
959 void
960 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
962     SPStar *star = SP_STAR(item);
964     Geom::Point const s = snap_knot_position(p);
966     if (star->flatsided == false) {
967         Geom::Point d = s - to_2geom(star->center);
969         double arg1 = atan2(d);
970         double darg1 = arg1 - star->arg[1];
972         if (state & GDK_MOD1_MASK) {
973             star->randomized = darg1/(star->arg[0] - star->arg[1]);
974         } else if (state & GDK_SHIFT_MASK) {
975             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
976         } else if (state & GDK_CONTROL_MASK) {
977             star->r[1]   = L2(d);
978             star->arg[1] = star->arg[0] + M_PI / star->sides;
979         }
980         else {
981             star->r[1]   = L2(d);
982             star->arg[1] = atan2(d);
983         }
984         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
985     }
988 Geom::Point
989 StarKnotHolderEntity1::knot_get()
991     g_assert(item != NULL);
993     SPStar *star = SP_STAR(item);
995     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
999 Geom::Point
1000 StarKnotHolderEntity2::knot_get()
1002     g_assert(item != NULL);
1004     SPStar *star = SP_STAR(item);
1006     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1009 static void
1010 sp_star_knot_click(SPItem *item, guint state)
1012     SPStar *star = SP_STAR(item);
1014     if (state & GDK_MOD1_MASK) {
1015         star->randomized = 0;
1016         ((SPObject *)star)->updateRepr();
1017     } else if (state & GDK_SHIFT_MASK) {
1018         star->rounded = 0;
1019         ((SPObject *)star)->updateRepr();
1020     } else if (state & GDK_CONTROL_MASK) {
1021         star->arg[1] = star->arg[0] + M_PI / star->sides;
1022         ((SPObject *)star)->updateRepr();
1023     }
1026 void
1027 StarKnotHolderEntity1::knot_click(guint state)
1029     return sp_star_knot_click(item, state);
1032 void
1033 StarKnotHolderEntity2::knot_click(guint state)
1035     return sp_star_knot_click(item, state);
1038 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1039     KnotHolder(desktop, item, relhandler)
1041     SPStar *star = SP_STAR(item);
1043     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1044     entity1->create(desktop, item, this,
1045                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1046                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1047     entity.push_back(entity1);
1049     if (star->flatsided == false) {
1050         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1051         entity2->create(desktop, item, this,
1052                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1053                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1054         entity.push_back(entity2);
1055     }
1057     add_pattern_knotholder();
1060 /* SPSpiral */
1062 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1063 public:
1064     virtual Geom::Point knot_get();
1065     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1066     virtual void knot_click(guint state);
1067 };
1069 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1070 public:
1071     virtual Geom::Point knot_get();
1072     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1073 };
1076 /*
1077  * set attributes via inner (t=t0) knot point:
1078  *   [default] increase/decrease inner point
1079  *   [shift]   increase/decrease inner and outer arg synchronizely
1080  *   [control] constrain inner arg to round per PI/4
1081  */
1082 void
1083 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1085     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1086     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1088     SPSpiral *spiral = SP_SPIRAL(item);
1090     gdouble   dx = p[Geom::X] - spiral->cx;
1091     gdouble   dy = p[Geom::Y] - spiral->cy;
1093     if (state & GDK_MOD1_MASK) {
1094         // adjust divergence by vertical drag, relative to rad
1095         double new_exp = (spiral->rad + dy)/(spiral->rad);
1096         spiral->exp = new_exp > 0? new_exp : 0;
1097     } else {
1098         // roll/unroll from inside
1099         gdouble   arg_t0;
1100         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1102         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1103         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1104         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1106         /* round inner arg per PI/snaps, if CTRL is pressed */
1107         if ( ( state & GDK_CONTROL_MASK )
1108              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1109              && ( snaps != 0 ) ) {
1110             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1111             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1112         }
1114         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1115     }
1117     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1120 /*
1121  * set attributes via outer (t=1) knot point:
1122  *   [default] increase/decrease revolution factor
1123  *   [control] constrain inner arg to round per PI/4
1124  */
1125 void
1126 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1128     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1129     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1131     SPSpiral *spiral = SP_SPIRAL(item);
1133     gdouble  dx = p[Geom::X] - spiral->cx;
1134     gdouble  dy = p[Geom::Y] - spiral->cy;
1136     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1137         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1138         if (!(state & GDK_MOD1_MASK)) {
1139             // if alt not pressed, change also rad; otherwise it is locked
1140             spiral->rad = MAX(hypot(dx, dy), 0.001);
1141         }
1142         if ( ( state & GDK_CONTROL_MASK )
1143              && snaps ) {
1144             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1145         }
1146     } else { // roll/unroll
1147         // arg of the spiral outer end
1148         double arg_1;
1149         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1151         // its fractional part after the whole turns are subtracted
1152         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1154         // arg of the mouse point relative to spiral center
1155         double mouse_angle = atan2(dy, dx);
1156         if (mouse_angle < 0)
1157             mouse_angle += 2*M_PI;
1159         // snap if ctrl
1160         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1161             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1162         }
1164         // by how much we want to rotate the outer point
1165         double diff = mouse_angle - arg_r;
1166         if (diff > M_PI)
1167             diff -= 2*M_PI;
1168         else if (diff < -M_PI)
1169             diff += 2*M_PI;
1171         // calculate the new rad;
1172         // the value of t corresponding to the angle arg_1 + diff:
1173         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1174         // the rad at that t:
1175         double rad_new = 0;
1176         if (t_temp > spiral->t0)
1177             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1179         // change the revo (converting diff from radians to the number of turns)
1180         spiral->revo += diff/(2*M_PI);
1181         if (spiral->revo < 1e-3)
1182             spiral->revo = 1e-3;
1184         // if alt not pressed and the values are sane, change the rad
1185         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1186             // adjust t0 too so that the inner point stays unmoved
1187             double r0;
1188             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1189             spiral->rad = rad_new;
1190             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1191         }
1192         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1193         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1194     }
1196     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1199 Geom::Point
1200 SpiralKnotHolderEntityInner::knot_get()
1202     SPSpiral *spiral = SP_SPIRAL(item);
1204     return sp_spiral_get_xy(spiral, spiral->t0);
1207 Geom::Point
1208 SpiralKnotHolderEntityOuter::knot_get()
1210     SPSpiral *spiral = SP_SPIRAL(item);
1212     return sp_spiral_get_xy(spiral, 1.0);
1215 void
1216 SpiralKnotHolderEntityInner::knot_click(guint state)
1218     SPSpiral *spiral = SP_SPIRAL(item);
1220     if (state & GDK_MOD1_MASK) {
1221         spiral->exp = 1;
1222         ((SPObject *)spiral)->updateRepr();
1223     } else if (state & GDK_SHIFT_MASK) {
1224         spiral->t0 = 0;
1225         ((SPObject *)spiral)->updateRepr();
1226     }
1229 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1230     KnotHolder(desktop, item, relhandler)
1232     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1233     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1234     entity_inner->create(desktop, item, this,
1235                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1236                            "with <b>Alt</b> to converge/diverge"));
1237     entity_outer->create(desktop, item, this,
1238                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1239                            "with <b>Shift</b> to scale/rotate"));
1240     entity.push_back(entity_inner);
1241     entity.push_back(entity_outer);
1243     add_pattern_knotholder();
1246 /* SPOffset */
1248 class OffsetKnotHolderEntity : public KnotHolderEntity {
1249 public:
1250     virtual Geom::Point knot_get();
1251     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1252 };
1254 void
1255 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1257     SPOffset *offset = SP_OFFSET(item);
1259     offset->rad = sp_offset_distance_to_original(offset, p);
1260     offset->knot = p;
1261     offset->knotSet = true;
1263     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1267 Geom::Point
1268 OffsetKnotHolderEntity::knot_get()
1270     SPOffset *offset = SP_OFFSET(item);
1272     Geom::Point np;
1273     sp_offset_top_point(offset,&np);
1274     return np;
1277 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1278     KnotHolder(desktop, item, relhandler)
1280     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1281     entity_offset->create(desktop, item, this,
1282                           _("Adjust the <b>offset distance</b>"));
1283     entity.push_back(entity_offset);
1285     add_pattern_knotholder();
1288 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1289 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1290 // sense logically.
1291 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1292 public:
1293     virtual Geom::Point knot_get();
1294     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1295 };
1297 Geom::Point
1298 FlowtextKnotHolderEntity::knot_get()
1300     SPRect *rect = SP_RECT(item);
1302     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1305 void
1306 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1308     set_internal(p, origin, state);
1311 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1312     KnotHolder(desktop, item, relhandler)
1314     g_assert(item != NULL);
1316     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1317     entity_flowtext->create(desktop, item, this,
1318                             _("Drag to resize the <b>flowed text frame</b>"));
1319     entity.push_back(entity_flowtext);
1322 /*
1323   Local Variables:
1324   mode:c++
1325   c-file-style:"stroustrup"
1326   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1327   indent-tabs-mode:nil
1328   fill-column:99
1329   End:
1330 */
1331 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :