Code

* Implement constrained snapping of knots
[inkscape.git] / src / object-edit.cpp
1 #define __SP_OBJECT_EDIT_C__
3 /*
4  * Node editing extension to objects
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Mitsuru Oka
9  *   Maximilian Albert <maximilian.albert@gmail.com>
10  *
11  * Licensed under GNU GPL
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
20 #include "sp-item.h"
21 #include "sp-rect.h"
22 #include "box3d.h"
23 #include "sp-ellipse.h"
24 #include "sp-star.h"
25 #include "sp-spiral.h"
26 #include "sp-offset.h"
27 #include "sp-flowtext.h"
28 #include "preferences.h"
29 #include "style.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "sp-namedview.h"
33 #include "live_effects/effect.h"
35 #include "sp-pattern.h"
36 #include "sp-path.h"
38 #include <glibmm/i18n.h>
40 #include "object-edit.h"
42 #include <libnr/nr-scale-ops.h>
44 #include "xml/repr.h"
46 #include "2geom/isnan.h"
48 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
50 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
51 {
52     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
54     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
55     effect->addHandles(knot_holder, desktop, item);
57     return knot_holder;
58 }
60 KnotHolder *
61 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
62 {
63     KnotHolder *knotholder = NULL;
65     if (SP_IS_LPE_ITEM(item) &&
66         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
67         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
68         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
69         knotholder = sp_lpe_knot_holder(item, desktop);
70     } else if (SP_IS_RECT(item)) {
71         knotholder = new RectKnotHolder(desktop, item, NULL);
72     } else if (SP_IS_BOX3D(item)) {
73         knotholder = new Box3DKnotHolder(desktop, item, NULL);
74     } else if (SP_IS_ARC(item)) {
75         knotholder = new ArcKnotHolder(desktop, item, NULL);
76     } else if (SP_IS_STAR(item)) {
77         knotholder = new StarKnotHolder(desktop, item, NULL);
78     } else if (SP_IS_SPIRAL(item)) {
79         knotholder = new SpiralKnotHolder(desktop, item, NULL);
80     } else if (SP_IS_OFFSET(item)) {
81         knotholder = new OffsetKnotHolder(desktop, item, NULL);
82     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
83         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
84     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
85                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
86         knotholder = new KnotHolder(desktop, item, NULL);
87         knotholder->add_pattern_knotholder();
88     }
90     return knotholder;
91 }
93 /* SPRect */
95 /* handle for horizontal rounding radius */
96 class RectKnotHolderEntityRX : public KnotHolderEntity {
97 public:
98     virtual Geom::Point knot_get();
99     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
100     virtual void knot_click(guint state);
101 };
103 /* handle for vertical rounding radius */
104 class RectKnotHolderEntityRY : public KnotHolderEntity {
105 public:
106     virtual Geom::Point knot_get();
107     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
108     virtual void knot_click(guint state);
109 };
111 /* handle for width/height adjustment */
112 class RectKnotHolderEntityWH : public KnotHolderEntity {
113 public:
114     virtual Geom::Point knot_get();
115     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
117 protected:
118     void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
119 };
121 /* handle for x/y adjustment */
122 class RectKnotHolderEntityXY : public KnotHolderEntity {
123 public:
124     virtual Geom::Point knot_get();
125     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
126 };
128 Geom::Point
129 RectKnotHolderEntityRX::knot_get()
131     SPRect *rect = SP_RECT(item);
133     return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
136 void
137 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
139     SPRect *rect = SP_RECT(item);
141     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
142     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
143     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
144     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
146     if (state & GDK_CONTROL_MASK) {
147         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
148         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
149         rect->rx._set = rect->ry._set = true;
151     } else {
152         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
153         rect->rx._set = true;
154     }
156     update_knot();
158     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
161 void
162 RectKnotHolderEntityRX::knot_click(guint state)
164     SPRect *rect = SP_RECT(item);
166     if (state & GDK_SHIFT_MASK) {
167         /* remove rounding from rectangle */
168         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
169         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
170     } else if (state & GDK_CONTROL_MASK) {
171         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
172         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
173     }
175     update_knot();
178 Geom::Point
179 RectKnotHolderEntityRY::knot_get()
181     SPRect *rect = SP_RECT(item);
183     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
186 void
187 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
189     SPRect *rect = SP_RECT(item);
191     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
192     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
193     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
194     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
196     if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
197                                     // resulting in a perfect circle (and not an ellipse)
198         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
199         rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
200         rect->ry._set = rect->rx._set = true;
201     } else {
202         if (!rect->rx._set || rect->rx.computed == 0) {
203             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
204                                       0.0,
205                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
206         } else {
207             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
208                                       0.0,
209                                       rect->height.computed / 2.0);
210         }
212         rect->ry._set = true;
213     }
215     update_knot();
217     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
220 void
221 RectKnotHolderEntityRY::knot_click(guint state)
223     SPRect *rect = SP_RECT(item);
225     if (state & GDK_SHIFT_MASK) {
226         /* remove rounding */
227         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
228         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
229     } else if (state & GDK_CONTROL_MASK) {
230         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
231         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
232     }
235 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
237 static void sp_rect_clamp_radii(SPRect *rect)
239     // clamp rounding radii so that they do not exceed width/height
240     if (2 * rect->rx.computed > rect->width.computed) {
241         rect->rx.computed = 0.5 * rect->width.computed;
242         rect->rx._set = true;
243     }
244     if (2 * rect->ry.computed > rect->height.computed) {
245         rect->ry.computed = 0.5 * rect->height.computed;
246         rect->ry._set = true;
247     }
250 Geom::Point
251 RectKnotHolderEntityWH::knot_get()
253     SPRect *rect = SP_RECT(item);
255     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
258 void
259 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
261     SPRect *rect = SP_RECT(item);
262     Geom::Point const s = snap_knot_position(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 const ratio = (w_orig / h_orig);
272         // mouse displacement since drag started
273         gdouble const minx = s[Geom::X] - origin[Geom::X];
274         gdouble const miny = s[Geom::Y] - origin[Geom::Y];
276         if (fabs(minx) > fabs(miny)) {
278             // snap to horizontal or diagonal
279             rect->width.computed = MAX(w_orig + minx, 0);
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                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
283             } else {
284                 // closer to the horizontal, change only width, height is h_orig
285                 rect->height.computed = MAX(h_orig, 0);
286             }
288         } else {
289             // snap to vertical or diagonal
290             rect->height.computed = MAX(h_orig + miny, 0);
291             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
292                 // closer to the diagonal and in same-sign quarters, change both using ratio
293                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
294             } else {
295                 // closer to the vertical, change only height, width is w_orig
296                 rect->width.computed = MAX(w_orig, 0);
297             }
298         }
300         rect->width._set = rect->height._set = true;
302     } else {
303         // move freely
304         rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
305         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
306         rect->width._set = rect->height._set = true;
307     }
309     sp_rect_clamp_radii(rect);
311     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
314 void
315 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
317     set_internal(p, origin, state);
318     update_knot();
321 Geom::Point
322 RectKnotHolderEntityXY::knot_get()
324     SPRect *rect = SP_RECT(item);
326     return Geom::Point(rect->x.computed, rect->y.computed);
329 void
330 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
332     SPRect *rect = SP_RECT(item);
334     // opposite corner (unmoved)
335     gdouble opposite_x = (rect->x.computed + rect->width.computed);
336     gdouble opposite_y = (rect->y.computed + rect->height.computed);
338     // original width/height when drag started
339     gdouble w_orig = opposite_x - origin[Geom::X];
340     gdouble h_orig = opposite_y - origin[Geom::Y];
342     Geom::Point const s = snap_knot_position(p);
344     // mouse displacement since drag started
345     gdouble minx = s[Geom::X] - origin[Geom::X];
346     gdouble miny = s[Geom::Y] - origin[Geom::Y];
348     if (state & GDK_CONTROL_MASK) {
349         //original ratio
350         gdouble ratio = (w_orig / h_orig);
352         if (fabs(minx) > fabs(miny)) {
354             // snap to horizontal or diagonal
355             rect->x.computed = MIN(s[Geom::X], opposite_x);
356             rect->width.computed = MAX(w_orig - minx, 0);
357             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
358                 // closer to the diagonal and in same-sign quarters, change both using ratio
359                 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
360                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
361             } else {
362                 // closer to the horizontal, change only width, height is h_orig
363                 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
364                 rect->height.computed = MAX(h_orig, 0);
365             }
367         } else {
369             // snap to vertical or diagonal
370             rect->y.computed = MIN(s[Geom::Y], opposite_y);
371             rect->height.computed = MAX(h_orig - miny, 0);
372             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
373                 // closer to the diagonal and in same-sign quarters, change both using ratio
374                 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
375                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
376             } else {
377                 // closer to the vertical, change only height, width is w_orig
378                 rect->x.computed = MIN(origin[Geom::X], opposite_x);
379                 rect->width.computed = MAX(w_orig, 0);
380             }
382         }
384         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
386     } else {
387         // move freely
388         rect->x.computed = MIN(s[Geom::X], opposite_x);
389         rect->width.computed = MAX(w_orig - minx, 0);
390         rect->y.computed = MIN(s[Geom::Y], opposite_y);
391         rect->height.computed = MAX(h_orig - miny, 0);
392         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
393     }
395     sp_rect_clamp_radii(rect);
397     update_knot();
399     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
402 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
403     KnotHolder(desktop, item, relhandler)
405     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
406     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
407     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
408     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
409     entity_rx->create(desktop, item, this,
410                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
411                         "to make the vertical radius the same"),
412                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
413     entity_ry->create(desktop, item, this,
414                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
415                         "to make the horizontal radius the same"),
416                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
417     entity_wh->create(desktop, item, this,
418                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
419                         "to lock ratio or stretch in one dimension only"),
420                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
421     entity_xy->create(desktop, item, this,
422                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
423                         "to lock ratio or stretch in one dimension only"),
424                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
425     entity.push_back(entity_rx);
426     entity.push_back(entity_ry);
427     entity.push_back(entity_wh);
428     entity.push_back(entity_xy);
430     add_pattern_knotholder();
433 /* Box3D (= the new 3D box structure) */
435 class Box3DKnotHolderEntity : public KnotHolderEntity {
436 public:
437     virtual Geom::Point knot_get() = 0;
438     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
440     Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
441     void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
442 };
444 Geom::Point
445 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
447     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
450 void
451 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
453     Geom::Point const s = snap_knot_position(new_pos);
455     g_assert(item != NULL);
456     SPBox3D *box = SP_BOX3D(item);
457     Geom::Matrix const i2d (sp_item_i2d_affine (item));
459     Box3D::Axis movement;
460     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
461         movement = Box3D::XY;
462     } else {
463         movement = Box3D::Z;
464     }
466     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
467     box3d_set_z_orders(box);
468     box3d_position_set(box);
471 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
472 public:
473     virtual Geom::Point knot_get();
474     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
475 };
477 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
478 public:
479     virtual Geom::Point knot_get();
480     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
481 };
483 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
484 public:
485     virtual Geom::Point knot_get();
486     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
487 };
489 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
490 public:
491     virtual Geom::Point knot_get();
492     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
493 };
495 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
496 public:
497     virtual Geom::Point knot_get();
498     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
499 };
501 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
502 public:
503     virtual Geom::Point knot_get();
504     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
505 };
507 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
508 public:
509     virtual Geom::Point knot_get();
510     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
511 };
513 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
514 public:
515     virtual Geom::Point knot_get();
516     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
517 };
519 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
520 public:
521     virtual Geom::Point knot_get();
522     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
523 };
525 Geom::Point
526 Box3DKnotHolderEntity0::knot_get()
528     return knot_get_generic(item, 0);
531 Geom::Point
532 Box3DKnotHolderEntity1::knot_get()
534     return knot_get_generic(item, 1);
537 Geom::Point
538 Box3DKnotHolderEntity2::knot_get()
540     return knot_get_generic(item, 2);
543 Geom::Point
544 Box3DKnotHolderEntity3::knot_get()
546     return knot_get_generic(item, 3);
549 Geom::Point
550 Box3DKnotHolderEntity4::knot_get()
552     return knot_get_generic(item, 4);
555 Geom::Point
556 Box3DKnotHolderEntity5::knot_get()
558     return knot_get_generic(item, 5);
561 Geom::Point
562 Box3DKnotHolderEntity6::knot_get()
564     return knot_get_generic(item, 6);
567 Geom::Point
568 Box3DKnotHolderEntity7::knot_get()
570     return knot_get_generic(item, 7);
573 Geom::Point
574 Box3DKnotHolderEntityCenter::knot_get()
576     return box3d_get_center_screen(SP_BOX3D(item));
579 void
580 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
582     knot_set_generic(item, 0, new_pos, state);
585 void
586 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
588     knot_set_generic(item, 1, new_pos, state);
591 void
592 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
594     knot_set_generic(item, 2, new_pos, state);
597 void
598 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
600     knot_set_generic(item, 3, new_pos, state);
603 void
604 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
606     knot_set_generic(item, 4, new_pos, state);
609 void
610 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
612     knot_set_generic(item, 5, new_pos, state);
615 void
616 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
618     knot_set_generic(item, 6, new_pos, state);
621 void
622 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
624     knot_set_generic(item, 7, new_pos, state);
627 void
628 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
630     Geom::Point const s = snap_knot_position(new_pos);
632     SPBox3D *box = SP_BOX3D(item);
633     Geom::Matrix const i2d (sp_item_i2d_affine (item));
635     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
636                       state & GDK_CONTROL_MASK);
638     box3d_set_z_orders(box);
639     box3d_position_set(box);
642 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
643     KnotHolder(desktop, item, relhandler)
645     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
646     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
647     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
648     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
649     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
650     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
651     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
652     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
653     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
655     entity_corner0->create(desktop, item, this,
656                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
657                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
658     entity_corner1->create(desktop, item, this,
659                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
660                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
661     entity_corner2->create(desktop, item, this,
662                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
663                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
664     entity_corner3->create(desktop, item, this,
665                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
666                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
667     entity_corner4->create(desktop, item, this,
668                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
669                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
670     entity_corner5->create(desktop, item, this,
671                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
672                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
673     entity_corner6->create(desktop, item, this,
674                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
675                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
676     entity_corner7->create(desktop, item, this,
677                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
678                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
679     entity_center->create(desktop, item, this,
680                           _("Move the box in perspective"),
681                           SP_KNOT_SHAPE_CROSS);
683     entity.push_back(entity_corner0);
684     entity.push_back(entity_corner1);
685     entity.push_back(entity_corner2);
686     entity.push_back(entity_corner3);
687     entity.push_back(entity_corner4);
688     entity.push_back(entity_corner5);
689     entity.push_back(entity_corner6);
690     entity.push_back(entity_corner7);
691     entity.push_back(entity_center);
693     add_pattern_knotholder();
696 /* SPArc */
698 class ArcKnotHolderEntityStart : public KnotHolderEntity {
699 public:
700     virtual Geom::Point knot_get();
701     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
702 };
704 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
705 public:
706     virtual Geom::Point knot_get();
707     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
708     virtual void knot_click(guint state);
709 };
711 class ArcKnotHolderEntityRX : public KnotHolderEntity {
712 public:
713     virtual Geom::Point knot_get();
714     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
715     virtual void knot_click(guint state);
716 };
718 class ArcKnotHolderEntityRY : public KnotHolderEntity {
719 public:
720     virtual Geom::Point knot_get();
721     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
722     virtual void knot_click(guint state);
723 };
725 /*
726  * return values:
727  *   1  : inside
728  *   0  : on the curves
729  *   -1 : outside
730  */
731 static gint
732 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
734     gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
735     gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
737     gdouble s = dx * dx + dy * dy;
738     if (s < 1.0) return 1;
739     if (s > 1.0) return -1;
740     return 0;
743 void
744 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
746     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
747     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
749     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
750     SPArc *arc = SP_ARC(item);
752     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
754     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
755     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
756     ge->start = atan2(delta * sc.inverse());
757     if ( ( state & GDK_CONTROL_MASK )
758          && snaps )
759     {
760         ge->start = sp_round(ge->start, M_PI/snaps);
761     }
762     sp_genericellipse_normalize(ge);
763     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
766 Geom::Point
767 ArcKnotHolderEntityStart::knot_get()
769     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
770     SPArc *arc = SP_ARC(item);
772     return sp_arc_get_xy(arc, ge->start);
775 void
776 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
778     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
779     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
781     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
782     SPArc *arc = SP_ARC(item);
784     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
786     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
787     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
788     ge->end = atan2(delta * sc.inverse());
789     if ( ( state & GDK_CONTROL_MASK )
790          && snaps )
791     {
792         ge->end = sp_round(ge->end, M_PI/snaps);
793     }
794     sp_genericellipse_normalize(ge);
795     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
798 Geom::Point
799 ArcKnotHolderEntityEnd::knot_get()
801     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
802     SPArc *arc = SP_ARC(item);
804     return sp_arc_get_xy(arc, ge->end);
808 void
809 ArcKnotHolderEntityEnd::knot_click(guint state)
811     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
813     if (state & GDK_SHIFT_MASK) {
814         ge->end = ge->start = 0;
815         ((SPObject *)ge)->updateRepr();
816     }
820 void
821 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
823     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
825     Geom::Point const s = snap_knot_position(p);
827     ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
829     if ( state & GDK_CONTROL_MASK ) {
830         ge->ry.computed = ge->rx.computed;
831     }
833     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
836 Geom::Point
837 ArcKnotHolderEntityRX::knot_get()
839     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
841     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(ge->rx.computed, 0));
844 void
845 ArcKnotHolderEntityRX::knot_click(guint state)
847     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
849     if (state & GDK_CONTROL_MASK) {
850         ge->ry.computed = ge->rx.computed;
851         ((SPObject *)ge)->updateRepr();
852     }
855 void
856 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
858     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
860     Geom::Point const s = snap_knot_position(p);
862     ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
864     if ( state & GDK_CONTROL_MASK ) {
865         ge->rx.computed = ge->ry.computed;
866     }
868     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
871 Geom::Point
872 ArcKnotHolderEntityRY::knot_get()
874     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
876     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(0, ge->ry.computed));
879 void
880 ArcKnotHolderEntityRY::knot_click(guint state)
882     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
884     if (state & GDK_CONTROL_MASK) {
885         ge->rx.computed = ge->ry.computed;
886         ((SPObject *)ge)->updateRepr();
887     }
890 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
891     KnotHolder(desktop, item, relhandler)
893     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
894     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
895     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
896     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
897     entity_rx->create(desktop, item, this,
898                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
899                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
900     entity_ry->create(desktop, item, this,
901                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
902                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
903     entity_start->create(desktop, item, this,
904                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
905                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
906                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
907     entity_end->create(desktop, item, this,
908                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
909                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
910                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
911     entity.push_back(entity_rx);
912     entity.push_back(entity_ry);
913     entity.push_back(entity_start);
914     entity.push_back(entity_end);
916     add_pattern_knotholder();
919 /* SPStar */
921 class StarKnotHolderEntity1 : public KnotHolderEntity {
922 public:
923     virtual Geom::Point knot_get();
924     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
925     virtual void knot_click(guint state);
926 };
928 class StarKnotHolderEntity2 : public KnotHolderEntity {
929 public:
930     virtual Geom::Point knot_get();
931     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
932     virtual void knot_click(guint state);
933 };
935 void
936 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
938     SPStar *star = SP_STAR(item);
940     Geom::Point const s = snap_knot_position(p);
942     Geom::Point d = s - to_2geom(star->center);
944     double arg1 = atan2(d);
945     double darg1 = arg1 - star->arg[0];
947     if (state & GDK_MOD1_MASK) {
948         star->randomized = darg1/(star->arg[0] - star->arg[1]);
949     } else if (state & GDK_SHIFT_MASK) {
950         star->rounded = darg1/(star->arg[0] - star->arg[1]);
951     } else if (state & GDK_CONTROL_MASK) {
952         star->r[0]    = L2(d);
953     } else {
954         star->r[0]    = L2(d);
955         star->arg[0]  = arg1;
956         star->arg[1] += darg1;
957     }
958     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
961 void
962 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
964     SPStar *star = SP_STAR(item);
966     Geom::Point const s = snap_knot_position(p);
968     if (star->flatsided == false) {
969         Geom::Point d = s - to_2geom(star->center);
971         double arg1 = atan2(d);
972         double darg1 = arg1 - star->arg[1];
974         if (state & GDK_MOD1_MASK) {
975             star->randomized = darg1/(star->arg[0] - star->arg[1]);
976         } else if (state & GDK_SHIFT_MASK) {
977             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
978         } else if (state & GDK_CONTROL_MASK) {
979             star->r[1]   = L2(d);
980             star->arg[1] = star->arg[0] + M_PI / star->sides;
981         }
982         else {
983             star->r[1]   = L2(d);
984             star->arg[1] = atan2(d);
985         }
986         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
987     }
990 Geom::Point
991 StarKnotHolderEntity1::knot_get()
993     g_assert(item != NULL);
995     SPStar *star = SP_STAR(item);
997     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1001 Geom::Point
1002 StarKnotHolderEntity2::knot_get()
1004     g_assert(item != NULL);
1006     SPStar *star = SP_STAR(item);
1008     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1011 static void
1012 sp_star_knot_click(SPItem *item, guint state)
1014     SPStar *star = SP_STAR(item);
1016     if (state & GDK_MOD1_MASK) {
1017         star->randomized = 0;
1018         ((SPObject *)star)->updateRepr();
1019     } else if (state & GDK_SHIFT_MASK) {
1020         star->rounded = 0;
1021         ((SPObject *)star)->updateRepr();
1022     } else if (state & GDK_CONTROL_MASK) {
1023         star->arg[1] = star->arg[0] + M_PI / star->sides;
1024         ((SPObject *)star)->updateRepr();
1025     }
1028 void
1029 StarKnotHolderEntity1::knot_click(guint state)
1031     return sp_star_knot_click(item, state);
1034 void
1035 StarKnotHolderEntity2::knot_click(guint state)
1037     return sp_star_knot_click(item, state);
1040 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1041     KnotHolder(desktop, item, relhandler)
1043     SPStar *star = SP_STAR(item);
1045     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1046     entity1->create(desktop, item, this,
1047                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1048                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1049     entity.push_back(entity1);
1051     if (star->flatsided == false) {
1052         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1053         entity2->create(desktop, item, this,
1054                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1055                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1056         entity.push_back(entity2);
1057     }
1059     add_pattern_knotholder();
1062 /* SPSpiral */
1064 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1065 public:
1066     virtual Geom::Point knot_get();
1067     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1068     virtual void knot_click(guint state);
1069 };
1071 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1072 public:
1073     virtual Geom::Point knot_get();
1074     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1075 };
1078 /*
1079  * set attributes via inner (t=t0) knot point:
1080  *   [default] increase/decrease inner point
1081  *   [shift]   increase/decrease inner and outer arg synchronizely
1082  *   [control] constrain inner arg to round per PI/4
1083  */
1084 void
1085 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1087     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1088     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1090     SPSpiral *spiral = SP_SPIRAL(item);
1092     gdouble   dx = p[Geom::X] - spiral->cx;
1093     gdouble   dy = p[Geom::Y] - spiral->cy;
1095     if (state & GDK_MOD1_MASK) {
1096         // adjust divergence by vertical drag, relative to rad
1097         double new_exp = (spiral->rad + dy)/(spiral->rad);
1098         spiral->exp = new_exp > 0? new_exp : 0;
1099     } else {
1100         // roll/unroll from inside
1101         gdouble   arg_t0;
1102         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1104         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1105         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1106         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1108         /* round inner arg per PI/snaps, if CTRL is pressed */
1109         if ( ( state & GDK_CONTROL_MASK )
1110              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1111              && ( snaps != 0 ) ) {
1112             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1113             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1114         }
1116         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1117     }
1119     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1122 /*
1123  * set attributes via outer (t=1) knot point:
1124  *   [default] increase/decrease revolution factor
1125  *   [control] constrain inner arg to round per PI/4
1126  */
1127 void
1128 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1130     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1131     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1133     SPSpiral *spiral = SP_SPIRAL(item);
1135     gdouble  dx = p[Geom::X] - spiral->cx;
1136     gdouble  dy = p[Geom::Y] - spiral->cy;
1138     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1139         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1140         if (!(state & GDK_MOD1_MASK)) {
1141             // if alt not pressed, change also rad; otherwise it is locked
1142             spiral->rad = MAX(hypot(dx, dy), 0.001);
1143         }
1144         if ( ( state & GDK_CONTROL_MASK )
1145              && snaps ) {
1146             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1147         }
1148     } else { // roll/unroll
1149         // arg of the spiral outer end
1150         double arg_1;
1151         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1153         // its fractional part after the whole turns are subtracted
1154         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1156         // arg of the mouse point relative to spiral center
1157         double mouse_angle = atan2(dy, dx);
1158         if (mouse_angle < 0)
1159             mouse_angle += 2*M_PI;
1161         // snap if ctrl
1162         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1163             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1164         }
1166         // by how much we want to rotate the outer point
1167         double diff = mouse_angle - arg_r;
1168         if (diff > M_PI)
1169             diff -= 2*M_PI;
1170         else if (diff < -M_PI)
1171             diff += 2*M_PI;
1173         // calculate the new rad;
1174         // the value of t corresponding to the angle arg_1 + diff:
1175         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1176         // the rad at that t:
1177         double rad_new = 0;
1178         if (t_temp > spiral->t0)
1179             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1181         // change the revo (converting diff from radians to the number of turns)
1182         spiral->revo += diff/(2*M_PI);
1183         if (spiral->revo < 1e-3)
1184             spiral->revo = 1e-3;
1186         // if alt not pressed and the values are sane, change the rad
1187         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1188             // adjust t0 too so that the inner point stays unmoved
1189             double r0;
1190             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1191             spiral->rad = rad_new;
1192             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1193         }
1194         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1195         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1196     }
1198     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1201 Geom::Point
1202 SpiralKnotHolderEntityInner::knot_get()
1204     SPSpiral *spiral = SP_SPIRAL(item);
1206     return sp_spiral_get_xy(spiral, spiral->t0);
1209 Geom::Point
1210 SpiralKnotHolderEntityOuter::knot_get()
1212     SPSpiral *spiral = SP_SPIRAL(item);
1214     return sp_spiral_get_xy(spiral, 1.0);
1217 void
1218 SpiralKnotHolderEntityInner::knot_click(guint state)
1220     SPSpiral *spiral = SP_SPIRAL(item);
1222     if (state & GDK_MOD1_MASK) {
1223         spiral->exp = 1;
1224         ((SPObject *)spiral)->updateRepr();
1225     } else if (state & GDK_SHIFT_MASK) {
1226         spiral->t0 = 0;
1227         ((SPObject *)spiral)->updateRepr();
1228     }
1231 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1232     KnotHolder(desktop, item, relhandler)
1234     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1235     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1236     entity_inner->create(desktop, item, this,
1237                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1238                            "with <b>Alt</b> to converge/diverge"));
1239     entity_outer->create(desktop, item, this,
1240                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1241                            "with <b>Shift</b> to scale/rotate"));
1242     entity.push_back(entity_inner);
1243     entity.push_back(entity_outer);
1245     add_pattern_knotholder();
1248 /* SPOffset */
1250 class OffsetKnotHolderEntity : public KnotHolderEntity {
1251 public:
1252     virtual Geom::Point knot_get();
1253     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1254 };
1256 void
1257 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1259     SPOffset *offset = SP_OFFSET(item);
1261     offset->rad = sp_offset_distance_to_original(offset, p);
1262     offset->knot = p;
1263     offset->knotSet = true;
1265     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1269 Geom::Point
1270 OffsetKnotHolderEntity::knot_get()
1272     SPOffset *offset = SP_OFFSET(item);
1274     Geom::Point np;
1275     sp_offset_top_point(offset,&np);
1276     return np;
1279 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1280     KnotHolder(desktop, item, relhandler)
1282     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1283     entity_offset->create(desktop, item, this,
1284                           _("Adjust the <b>offset distance</b>"));
1285     entity.push_back(entity_offset);
1287     add_pattern_knotholder();
1290 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1291 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1292 // sense logically.
1293 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1294 public:
1295     virtual Geom::Point knot_get();
1296     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1297 };
1299 Geom::Point
1300 FlowtextKnotHolderEntity::knot_get()
1302     SPRect *rect = SP_RECT(item);
1304     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1307 void
1308 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1310     set_internal(p, origin, state);
1313 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1314     KnotHolder(desktop, item, relhandler)
1316     g_assert(item != NULL);
1318     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1319     entity_flowtext->create(desktop, item, this,
1320                             _("Drag to resize the <b>flowed text frame</b>"));
1321     entity.push_back(entity_flowtext);
1324 /*
1325   Local Variables:
1326   mode:c++
1327   c-file-style:"stroustrup"
1328   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1329   indent-tabs-mode:nil
1330   fill-column:99
1331   End:
1332 */
1333 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :