Code

Some cleanup of knotholder code; mostly renaming knot_(get|set|click)_func --> knot_...
[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 "prefs-utils.h"
29 #include "inkscape.h"
30 #include "snap.h"
31 #include "desktop-affine.h"
32 #include "style.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "sp-namedview.h"
36 #include "live_effects/effect.h"
38 #include "sp-pattern.h"
39 #include "sp-path.h"
41 #include <glibmm/i18n.h>
43 #include "object-edit.h"
45 #include <libnr/nr-scale-ops.h>
46 #include <libnr/nr-matrix-div.h>
48 #include "xml/repr.h"
50 #include "2geom/isnan.h"
52 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
54 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
55 {
56     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
58     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
59     if (!effect) {
60         g_error("sp_lpe_knot_holder: logical error, this method cannot be called with item having an LPE");
61     } else {
62         effect->addHandles(knot_holder);
63     }
65     return knot_holder;
66 }
68 KnotHolder *
69 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
70 {
71     KnotHolder *knotholder = NULL;
73     if (SP_IS_LPE_ITEM(item) &&
74         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
75         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
76         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
77         knotholder = sp_lpe_knot_holder(item, desktop);
78     } else if (SP_IS_RECT(item)) {
79         knotholder = new RectKnotHolder(desktop, item, NULL);
80     } else if (SP_IS_BOX3D(item)) {
81         knotholder = new Box3DKnotHolder(desktop, item, NULL);
82     } else if (SP_IS_ARC(item)) {
83         knotholder = new ArcKnotHolder(desktop, item, NULL);
84     } else if (SP_IS_STAR(item)) {
85         knotholder = new StarKnotHolder(desktop, item, NULL);
86     } else if (SP_IS_SPIRAL(item)) {
87         knotholder = new SpiralKnotHolder(desktop, item, NULL);
88     } else if (SP_IS_OFFSET(item)) {
89         knotholder = new OffsetKnotHolder(desktop, item, NULL);
90     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
91         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
92     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
93                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
94         knotholder->add_pattern_knotholder();
95     }
97     return knotholder;
98 }
100 /* SPRect */
102 /* handle for horizontal rounding radius */
103 class RectKnotHolderEntityRX : public KnotHolderEntity {
104 public:
105     virtual NR::Point knot_get();
106     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
107     virtual void knot_click(guint state);
108 };
110 /* handle for vertical rounding radius */
111 class RectKnotHolderEntityRY : public KnotHolderEntity {
112 public:
113     virtual NR::Point knot_get();
114     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
115     virtual void knot_click(guint state);
116 };
118 /* handle for width/height adjustment */
119 class RectKnotHolderEntityWH : public KnotHolderEntity {
120 public:
121     virtual NR::Point knot_get();
122     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
123 };
125 /* handle for x/y adjustment */
126 class RectKnotHolderEntityXY : public KnotHolderEntity {
127 public:
128     virtual NR::Point knot_get();
129     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
130 };
132 static NR::Point snap_knot_position(SPItem *item, NR::Point const &p)
134     SPDesktop const *desktop = inkscape_active_desktop();
135     NR::Matrix const i2d (sp_item_i2d_affine (item));
136     NR::Point s = p * i2d;
137     SnapManager &m = desktop->namedview->snap_manager;
138     m.setup(desktop, item);
139     m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, s);
140     return s * i2d.inverse();
143 NR::Point
144 RectKnotHolderEntityRX::knot_get()
146     SPRect *rect = SP_RECT(item);
148     return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
151 void
152 RectKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
154     SPRect *rect = SP_RECT(item);
156     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
157     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
158     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
160     if (state & GDK_CONTROL_MASK) {
161         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
162         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
163         rect->rx._set = rect->ry._set = true;
165     } else {
166         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
167         rect->rx._set = true;
168     }
170     update_knot();
172     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
175 void
176 RectKnotHolderEntityRX::knot_click(guint state)
178     SPRect *rect = SP_RECT(item);
180     if (state & GDK_SHIFT_MASK) {
181         /* remove rounding from rectangle */
182         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
183         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
184     } else if (state & GDK_CONTROL_MASK) {
185         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
186         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
187     }
189     update_knot();
192 NR::Point
193 RectKnotHolderEntityRY::knot_get()
195     SPRect *rect = SP_RECT(item);
197     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
200 void
201 RectKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
203     SPRect *rect = SP_RECT(item);
205     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
206     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
207     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
209     if (state & GDK_CONTROL_MASK) {
210         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
211         rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
212         rect->ry._set = rect->rx._set = true;
213     } else {
214         if (!rect->rx._set || rect->rx.computed == 0) {
215             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
216                                       0.0,
217                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
218         } else {
219             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
220                                       0.0,
221                                       rect->height.computed / 2.0);
222         }
224         rect->ry._set = true;
225     }
227     update_knot();
229     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
232 void
233 RectKnotHolderEntityRY::knot_click(guint state)
235     SPRect *rect = SP_RECT(item);
237     if (state & GDK_SHIFT_MASK) {
238         /* remove rounding */
239         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
240         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
241     } else if (state & GDK_CONTROL_MASK) {
242         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
243         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
244     }
247 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
249 static void sp_rect_clamp_radii(SPRect *rect)
251     // clamp rounding radii so that they do not exceed width/height
252     if (2 * rect->rx.computed > rect->width.computed) {
253         rect->rx.computed = 0.5 * rect->width.computed;
254         rect->rx._set = true;
255     }
256     if (2 * rect->ry.computed > rect->height.computed) {
257         rect->ry.computed = 0.5 * rect->height.computed;
258         rect->ry._set = true;
259     }
262 NR::Point
263 RectKnotHolderEntityWH::knot_get()
265     SPRect *rect = SP_RECT(item);
267     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
270 static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
272     NR::Point const s = snap_knot_position(rect, p);
274     if (state & GDK_CONTROL_MASK) {
275         // original width/height when drag started
276         gdouble const w_orig = (origin[NR::X] - rect->x.computed);
277         gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
279         //original ratio
280         gdouble const ratio = (w_orig / h_orig);
282         // mouse displacement since drag started
283         gdouble const minx = s[NR::X] - origin[NR::X];
284         gdouble const miny = s[NR::Y] - origin[NR::Y];
286         if (fabs(minx) > fabs(miny)) {
288             // snap to horizontal or diagonal
289             rect->width.computed = MAX(w_orig + minx, 0);
290             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
291                 // closer to the diagonal and in same-sign quarters, change both using ratio
292                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
293             } else {
294                 // closer to the horizontal, change only width, height is h_orig
295                 rect->height.computed = MAX(h_orig, 0);
296             }
298         } else {
299             // snap to vertical or diagonal
300             rect->height.computed = MAX(h_orig + miny, 0);
301             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
302                 // closer to the diagonal and in same-sign quarters, change both using ratio
303                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
304             } else {
305                 // closer to the vertical, change only height, width is w_orig
306                 rect->width.computed = MAX(w_orig, 0);
307             }
308         }
310         rect->width._set = rect->height._set = true;
312     } else {
313         // move freely
314         rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
315         rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
316         rect->width._set = rect->height._set = true;
317     }
319     sp_rect_clamp_radii(rect);
321     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
324 void
325 RectKnotHolderEntityWH::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
327     SPRect *rect = SP_RECT(item);
329     sp_rect_wh_set_internal(rect, p, origin, state);
331     update_knot();
334 NR::Point
335 RectKnotHolderEntityXY::knot_get()
337     SPRect *rect = SP_RECT(item);
339     return NR::Point(rect->x.computed, rect->y.computed);
342 void
343 RectKnotHolderEntityXY::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
345     SPRect *rect = SP_RECT(item);
347     // opposite corner (unmoved)
348     gdouble opposite_x = (rect->x.computed + rect->width.computed);
349     gdouble opposite_y = (rect->y.computed + rect->height.computed);
351     // original width/height when drag started
352     gdouble w_orig = opposite_x - origin[NR::X];
353     gdouble h_orig = opposite_y - origin[NR::Y];
355     NR::Point const s = snap_knot_position(rect, p);
357     // mouse displacement since drag started
358     gdouble minx = s[NR::X] - origin[NR::X];
359     gdouble miny = s[NR::Y] - origin[NR::Y];
361     if (state & GDK_CONTROL_MASK) {
362         //original ratio
363         gdouble ratio = (w_orig / h_orig);
365         if (fabs(minx) > fabs(miny)) {
367             // snap to horizontal or diagonal
368             rect->x.computed = MIN(s[NR::X], opposite_x);
369             rect->width.computed = MAX(w_orig - minx, 0);
370             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
371                 // closer to the diagonal and in same-sign quarters, change both using ratio
372                 rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
373                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
374             } else {
375                 // closer to the horizontal, change only width, height is h_orig
376                 rect->y.computed = MIN(origin[NR::Y], opposite_y);
377                 rect->height.computed = MAX(h_orig, 0);
378             }
380         } else {
382             // snap to vertical or diagonal
383             rect->y.computed = MIN(s[NR::Y], opposite_y);
384             rect->height.computed = MAX(h_orig - miny, 0);
385             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
386                 // closer to the diagonal and in same-sign quarters, change both using ratio
387                 rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
388                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
389             } else {
390                 // closer to the vertical, change only height, width is w_orig
391                 rect->x.computed = MIN(origin[NR::X], opposite_x);
392                 rect->width.computed = MAX(w_orig, 0);
393             }
395         }
397         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
399     } else {
400         // move freely
401         rect->x.computed = MIN(s[NR::X], opposite_x);
402         rect->width.computed = MAX(w_orig - minx, 0);
403         rect->y.computed = MIN(s[NR::Y], opposite_y);
404         rect->height.computed = MAX(h_orig - miny, 0);
405         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
406     }
408     sp_rect_clamp_radii(rect);
410     update_knot();
412     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
415 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
416     KnotHolder(desktop, item, relhandler)
418     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
419     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
420     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
421     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
422     entity_rx->create(desktop, item, this,
423                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
424                         "to make the vertical radius the same"),
425                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
426     entity_ry->create(desktop, item, this,
427                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
428                         "to make the horizontal radius the same"),
429                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
430     entity_wh->create(desktop, item, this,
431                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
432                         "to lock ratio or stretch in one dimension only"),
433                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
434     entity_xy->create(desktop, item, this,
435                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
436                         "to lock ratio or stretch in one dimension only"),
437                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
438     entity.push_back(entity_rx);
439     entity.push_back(entity_ry);
440     entity.push_back(entity_wh);
441     entity.push_back(entity_xy);
443     add_pattern_knotholder();
446 /* Box3D (= the new 3D box structure) */
448 class Box3DKnotHolderEntity : public KnotHolderEntity {
449 public:
450     virtual NR::Point knot_get() = 0;
451     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state) = 0;
453     static NR::Point knot_get_generic(SPItem *item, unsigned int knot_id);
454     static void knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &p, guint state);
455 };
457 NR::Point
458 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
460     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
463 void
464 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &new_pos, guint state)
466     NR::Point const s = snap_knot_position(item, new_pos);
468     g_assert(item != NULL);
469     SPBox3D *box = SP_BOX3D(item);
470     NR::Matrix const i2d (sp_item_i2d_affine (item));
472     Box3D::Axis movement;
473     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
474         movement = Box3D::XY;
475     } else {
476         movement = Box3D::Z;
477     }
479     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
480     box3d_set_z_orders(box);
481     box3d_position_set(box);
484 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
485 public:
486     virtual NR::Point knot_get();
487     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
488 };
490 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
491 public:
492     virtual NR::Point knot_get();
493     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
494 };
496 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
497 public:
498     virtual NR::Point knot_get();
499     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
500 };
502 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
503 public:
504     virtual NR::Point knot_get();
505     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
506 };
508 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
509 public:
510     virtual NR::Point knot_get();
511     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
512 };
514 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
515 public:
516     virtual NR::Point knot_get();
517     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
518 };
520 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
521 public:
522     virtual NR::Point knot_get();
523     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
524 };
526 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
527 public:
528     virtual NR::Point knot_get();
529     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
530 };
532 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
533 public:
534     virtual NR::Point knot_get();
535     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
536 };
538 NR::Point
539 Box3DKnotHolderEntity0::knot_get()
541     return knot_get_generic(item, 0);
544 NR::Point
545 Box3DKnotHolderEntity1::knot_get()
547     return knot_get_generic(item, 1);
550 NR::Point
551 Box3DKnotHolderEntity2::knot_get()
553     return knot_get_generic(item, 2);
556 NR::Point
557 Box3DKnotHolderEntity3::knot_get()
559     return knot_get_generic(item, 3);
562 NR::Point
563 Box3DKnotHolderEntity4::knot_get()
565     return knot_get_generic(item, 4);
568 NR::Point
569 Box3DKnotHolderEntity5::knot_get()
571     return knot_get_generic(item, 5);
574 NR::Point
575 Box3DKnotHolderEntity6::knot_get()
577     return knot_get_generic(item, 6);
580 NR::Point
581 Box3DKnotHolderEntity7::knot_get()
583     return knot_get_generic(item, 7);
586 NR::Point
587 Box3DKnotHolderEntityCenter::knot_get()
589     return box3d_get_center_screen(SP_BOX3D(item));
592 void
593 Box3DKnotHolderEntity0::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
595     knot_set_generic(item, 0, new_pos, state);
598 void
599 Box3DKnotHolderEntity1::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
601     knot_set_generic(item, 1, new_pos, state);
604 void
605 Box3DKnotHolderEntity2::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
607     knot_set_generic(item, 2, new_pos, state);
610 void
611 Box3DKnotHolderEntity3::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
613     knot_set_generic(item, 3, new_pos, state);
616 void
617 Box3DKnotHolderEntity4::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
619     knot_set_generic(item, 4, new_pos, state);
622 void
623 Box3DKnotHolderEntity5::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
625     knot_set_generic(item, 5, new_pos, state);
628 void
629 Box3DKnotHolderEntity6::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
631     knot_set_generic(item, 6, new_pos, state);
634 void
635 Box3DKnotHolderEntity7::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
637     knot_set_generic(item, 7, new_pos, state);
640 void
641 Box3DKnotHolderEntityCenter::knot_set(NR::Point const &new_pos, NR::Point const &origin, guint state)
643     NR::Point const s = snap_knot_position(item, new_pos);
645     SPBox3D *box = SP_BOX3D(item);
646     NR::Matrix const i2d (sp_item_i2d_affine (item));
648     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
649                       state & GDK_CONTROL_MASK);
651     box3d_set_z_orders(box);
652     box3d_position_set(box);
655 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
656     KnotHolder(desktop, item, relhandler)
658     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
659     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
660     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
661     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
662     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
663     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
664     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
665     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
666     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
668     entity_corner0->create(desktop, item, this,
669                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
670                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
671     entity_corner1->create(desktop, item, this,
672                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
673                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
674     entity_corner2->create(desktop, item, this,
675                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
676                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
677     entity_corner3->create(desktop, item, this,
678                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
679                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
680     entity_corner4->create(desktop, item, this,
681                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
682                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
683     entity_corner5->create(desktop, item, this,
684                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
685                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
686     entity_corner6->create(desktop, item, this,
687                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
688                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
689     entity_corner7->create(desktop, item, this,
690                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
691                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
692     entity_center->create(desktop, item, this,
693                           _("Move the box in perspective"),
694                           SP_KNOT_SHAPE_CROSS);
696     entity.push_back(entity_corner0);
697     entity.push_back(entity_corner1);
698     entity.push_back(entity_corner2);
699     entity.push_back(entity_corner3);
700     entity.push_back(entity_corner4);
701     entity.push_back(entity_corner5);
702     entity.push_back(entity_corner6);
703     entity.push_back(entity_corner7);
704     entity.push_back(entity_center);
706     add_pattern_knotholder();
709 /* SPArc */
711 class ArcKnotHolderEntityStart : public KnotHolderEntity {
712 public:
713     virtual NR::Point knot_get();
714     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
715 };
717 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
718 public:
719     virtual NR::Point knot_get();
720     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
721     virtual void knot_click(guint state);
722 };
724 class ArcKnotHolderEntityRX : public KnotHolderEntity {
725 public:
726     virtual NR::Point knot_get();
727     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
728     virtual void knot_click(guint state);
729 };
731 class ArcKnotHolderEntityRY : public KnotHolderEntity {
732 public:
733     virtual NR::Point knot_get();
734     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
735     virtual void knot_click(guint state);
736 };
738 /*
739  * return values:
740  *   1  : inside
741  *   0  : on the curves
742  *   -1 : outside
743  */
744 static gint
745 sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
747     gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
748     gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
750     gdouble s = dx * dx + dy * dy;
751     if (s < 1.0) return 1;
752     if (s > 1.0) return -1;
753     return 0;
756 void
757 ArcKnotHolderEntityStart::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
759     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
761     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
762     SPArc *arc = SP_ARC(item);
764     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
766     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
767     NR::scale sc(ge->rx.computed, ge->ry.computed);
768     ge->start = atan2(delta * sc.inverse());
769     if ( ( state & GDK_CONTROL_MASK )
770          && snaps )
771     {
772         ge->start = sp_round(ge->start, M_PI/snaps);
773     }
774     sp_genericellipse_normalize(ge);
775     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
778 NR::Point
779 ArcKnotHolderEntityStart::knot_get()
781     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
782     SPArc *arc = SP_ARC(item);
784     return sp_arc_get_xy(arc, ge->start);
787 void
788 ArcKnotHolderEntityEnd::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
790     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
792     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
793     SPArc *arc = SP_ARC(item);
795     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
797     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
798     NR::scale sc(ge->rx.computed, ge->ry.computed);
799     ge->end = atan2(delta * sc.inverse());
800     if ( ( state & GDK_CONTROL_MASK )
801          && snaps )
802     {
803         ge->end = sp_round(ge->end, M_PI/snaps);
804     }
805     sp_genericellipse_normalize(ge);
806     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
809 NR::Point
810 ArcKnotHolderEntityEnd::knot_get()
812     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
813     SPArc *arc = SP_ARC(item);
815     return sp_arc_get_xy(arc, ge->end);
819 void
820 ArcKnotHolderEntityEnd::knot_click(guint state)
822     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
824     if (state & GDK_SHIFT_MASK) {
825         ge->end = ge->start = 0;
826         ((SPObject *)ge)->updateRepr();
827     }
831 void
832 ArcKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
834     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
835     SPArc *arc = SP_ARC(item);
837     NR::Point const s = snap_knot_position(arc, p);
839     ge->rx.computed = fabs( ge->cx.computed - s[NR::X] );
841     if ( state & GDK_CONTROL_MASK ) {
842         ge->ry.computed = ge->rx.computed;
843     }
845     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
848 NR::Point
849 ArcKnotHolderEntityRX::knot_get()
851     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
853     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(ge->rx.computed, 0));
856 void
857 ArcKnotHolderEntityRX::knot_click(guint state)
859     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
861     if (state & GDK_CONTROL_MASK) {
862         ge->ry.computed = ge->rx.computed;
863         ((SPObject *)ge)->updateRepr();
864     }
867 void
868 ArcKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
870     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
871     SPArc *arc = SP_ARC(item);
873     NR::Point const s = snap_knot_position(arc, p);
875     ge->ry.computed = fabs( ge->cy.computed - s[NR::Y] );
877     if ( state & GDK_CONTROL_MASK ) {
878         ge->rx.computed = ge->ry.computed;
879     }
881     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
884 NR::Point
885 ArcKnotHolderEntityRY::knot_get()
887     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
889     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(0, ge->ry.computed));
892 void
893 ArcKnotHolderEntityRY::knot_click(guint state)
895     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
897     if (state & GDK_CONTROL_MASK) {
898         ge->rx.computed = ge->ry.computed;
899         ((SPObject *)ge)->updateRepr();
900     }
903 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
904     KnotHolder(desktop, item, relhandler)
906     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
907     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
908     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
909     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
910     entity_rx->create(desktop, item, this,
911                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
912                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
913     entity_ry->create(desktop, item, this,
914                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
915                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
916     entity_start->create(desktop, item, this,
917                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
918                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
919                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
920     entity_end->create(desktop, item, this,
921                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
922                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
923                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
924     entity.push_back(entity_rx);
925     entity.push_back(entity_ry);
926     entity.push_back(entity_start);
927     entity.push_back(entity_end);
929     add_pattern_knotholder();
932 /* SPStar */
934 class StarKnotHolderEntity1 : public KnotHolderEntity {
935 public:
936     virtual NR::Point knot_get();
937     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
938     virtual void knot_click(guint state);
939 };
941 class StarKnotHolderEntity2 : public KnotHolderEntity {
942 public:
943     virtual NR::Point knot_get();
944     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
945     virtual void knot_click(guint state);
946 };
948 void
949 StarKnotHolderEntity1::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
951     SPStar *star = SP_STAR(item);
953     NR::Point const s = snap_knot_position(star, p);
955     NR::Point d = s - star->center;
957     double arg1 = atan2(d);
958     double darg1 = arg1 - star->arg[0];
960     if (state & GDK_MOD1_MASK) {
961         star->randomized = darg1/(star->arg[0] - star->arg[1]);
962     } else if (state & GDK_SHIFT_MASK) {
963         star->rounded = darg1/(star->arg[0] - star->arg[1]);
964     } else if (state & GDK_CONTROL_MASK) {
965         star->r[0]    = L2(d);
966     } else {
967         star->r[0]    = L2(d);
968         star->arg[0]  = arg1;
969         star->arg[1] += darg1;
970     }
971     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
974 void
975 StarKnotHolderEntity2::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
977     SPStar *star = SP_STAR(item);
979     NR::Point const s = snap_knot_position(star, p);
981     if (star->flatsided == false) {
982         NR::Point d = s - star->center;
984         double arg1 = atan2(d);
985         double darg1 = arg1 - star->arg[1];
987         if (state & GDK_MOD1_MASK) {
988             star->randomized = darg1/(star->arg[0] - star->arg[1]);
989         } else if (state & GDK_SHIFT_MASK) {
990             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
991         } else if (state & GDK_CONTROL_MASK) {
992             star->r[1]   = L2(d);
993             star->arg[1] = star->arg[0] + M_PI / star->sides;
994         }
995         else {
996             star->r[1]   = L2(d);
997             star->arg[1] = atan2(d);
998         }
999         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1000     }
1003 NR::Point
1004 StarKnotHolderEntity1::knot_get()
1006     g_assert(item != NULL);
1008     SPStar *star = SP_STAR(item);
1010     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1014 NR::Point
1015 StarKnotHolderEntity2::knot_get()
1017     g_assert(item != NULL);
1019     SPStar *star = SP_STAR(item);
1021     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1024 static void
1025 sp_star_knot_click(SPItem *item, guint state)
1027     SPStar *star = SP_STAR(item);
1029     if (state & GDK_MOD1_MASK) {
1030         star->randomized = 0;
1031         ((SPObject *)star)->updateRepr();
1032     } else if (state & GDK_SHIFT_MASK) {
1033         star->rounded = 0;
1034         ((SPObject *)star)->updateRepr();
1035     } else if (state & GDK_CONTROL_MASK) {
1036         star->arg[1] = star->arg[0] + M_PI / star->sides;
1037         ((SPObject *)star)->updateRepr();
1038     }
1041 void
1042 StarKnotHolderEntity1::knot_click(guint state)
1044     return sp_star_knot_click(item, state);
1047 void
1048 StarKnotHolderEntity2::knot_click(guint state)
1050     return sp_star_knot_click(item, state);
1053 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1054     KnotHolder(desktop, item, relhandler)
1056     SPStar *star = SP_STAR(item);
1058     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1059     entity1->create(desktop, item, this,
1060                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1061                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1062     entity.push_back(entity1);
1064     if (star->flatsided == false) {
1065         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1066         entity2->create(desktop, item, this,
1067                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1068                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1069         entity.push_back(entity2);
1070     }
1072     add_pattern_knotholder();
1075 /* SPSpiral */
1077 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1078 public:
1079     virtual NR::Point knot_get();
1080     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1081     virtual void knot_click(guint state);
1082 };
1084 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1085 public:
1086     virtual NR::Point knot_get();
1087     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1088 };
1091 /*
1092  * set attributes via inner (t=t0) knot point:
1093  *   [default] increase/decrease inner point
1094  *   [shift]   increase/decrease inner and outer arg synchronizely
1095  *   [control] constrain inner arg to round per PI/4
1096  */
1097 void
1098 SpiralKnotHolderEntityInner::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1100     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1102     SPSpiral *spiral = SP_SPIRAL(item);
1104     gdouble   dx = p[NR::X] - spiral->cx;
1105     gdouble   dy = p[NR::Y] - spiral->cy;
1107     if (state & GDK_MOD1_MASK) {
1108         // adjust divergence by vertical drag, relative to rad
1109         double new_exp = (spiral->rad + dy)/(spiral->rad);
1110         spiral->exp = new_exp > 0? new_exp : 0;
1111     } else {
1112         // roll/unroll from inside
1113         gdouble   arg_t0;
1114         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1116         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1117         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1118         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1120         /* round inner arg per PI/snaps, if CTRL is pressed */
1121         if ( ( state & GDK_CONTROL_MASK )
1122              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1123              && ( snaps != 0 ) ) {
1124             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1125             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1126         }
1128         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1129     }
1131     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1134 /*
1135  * set attributes via outer (t=1) knot point:
1136  *   [default] increase/decrease revolution factor
1137  *   [control] constrain inner arg to round per PI/4
1138  */
1139 void
1140 SpiralKnotHolderEntityOuter::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1142     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1144     SPSpiral *spiral = SP_SPIRAL(item);
1146     gdouble  dx = p[NR::X] - spiral->cx;
1147     gdouble  dy = p[NR::Y] - spiral->cy;
1149     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1150         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1151         if (!(state & GDK_MOD1_MASK)) {
1152             // if alt not pressed, change also rad; otherwise it is locked
1153             spiral->rad = MAX(hypot(dx, dy), 0.001);
1154         }
1155         if ( ( state & GDK_CONTROL_MASK )
1156              && snaps ) {
1157             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1158         }
1159     } else { // roll/unroll
1160         // arg of the spiral outer end
1161         double arg_1;
1162         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1164         // its fractional part after the whole turns are subtracted
1165         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1167         // arg of the mouse point relative to spiral center
1168         double mouse_angle = atan2(dy, dx);
1169         if (mouse_angle < 0)
1170             mouse_angle += 2*M_PI;
1172         // snap if ctrl
1173         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1174             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1175         }
1177         // by how much we want to rotate the outer point
1178         double diff = mouse_angle - arg_r;
1179         if (diff > M_PI)
1180             diff -= 2*M_PI;
1181         else if (diff < -M_PI)
1182             diff += 2*M_PI;
1184         // calculate the new rad;
1185         // the value of t corresponding to the angle arg_1 + diff:
1186         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1187         // the rad at that t:
1188         double rad_new = 0;
1189         if (t_temp > spiral->t0)
1190             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1192         // change the revo (converting diff from radians to the number of turns)
1193         spiral->revo += diff/(2*M_PI);
1194         if (spiral->revo < 1e-3)
1195             spiral->revo = 1e-3;
1197         // if alt not pressed and the values are sane, change the rad
1198         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1199             // adjust t0 too so that the inner point stays unmoved
1200             double r0;
1201             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1202             spiral->rad = rad_new;
1203             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1204         }
1205         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1206         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1207     }
1209     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1212 NR::Point
1213 SpiralKnotHolderEntityInner::knot_get()
1215     SPSpiral *spiral = SP_SPIRAL(item);
1217     return sp_spiral_get_xy(spiral, spiral->t0);
1220 NR::Point
1221 SpiralKnotHolderEntityOuter::knot_get()
1223     SPSpiral *spiral = SP_SPIRAL(item);
1225     return sp_spiral_get_xy(spiral, 1.0);
1228 void
1229 SpiralKnotHolderEntityInner::knot_click(guint state)
1231     SPSpiral *spiral = SP_SPIRAL(item);
1233     if (state & GDK_MOD1_MASK) {
1234         spiral->exp = 1;
1235         ((SPObject *)spiral)->updateRepr();
1236     } else if (state & GDK_SHIFT_MASK) {
1237         spiral->t0 = 0;
1238         ((SPObject *)spiral)->updateRepr();
1239     }
1242 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1243     KnotHolder(desktop, item, relhandler)
1245     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1246     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1247     entity_inner->create(desktop, item, this,
1248                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1249                            "with <b>Alt</b> to converge/diverge"));
1250     entity_outer->create(desktop, item, this,
1251                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1252                            "with <b>Shift</b> to scale/rotate"));
1253     entity.push_back(entity_inner);
1254     entity.push_back(entity_outer);
1256     add_pattern_knotholder();
1259 /* SPOffset */
1261 class OffsetKnotHolderEntity : public KnotHolderEntity {
1262 public:
1263     virtual NR::Point knot_get();
1264     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1265 };
1267 void
1268 OffsetKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
1270     SPOffset *offset = SP_OFFSET(item);
1272     offset->rad = sp_offset_distance_to_original(offset, p);
1273     offset->knot = p;
1274     offset->knotSet = true;
1276     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1280 NR::Point
1281 OffsetKnotHolderEntity::knot_get()
1283     SPOffset *offset = SP_OFFSET(item);
1285     NR::Point np;
1286     sp_offset_top_point(offset,&np);
1287     return np;
1290 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1291     KnotHolder(desktop, item, relhandler)
1293     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1294     entity_offset->create(desktop, item, this,
1295                           _("Adjust the <b>offset distance</b>"));
1296     entity.push_back(entity_offset);
1298     add_pattern_knotholder();
1301 class FlowtextKnotHolderEntity : public KnotHolderEntity {
1302 public:
1303     virtual NR::Point knot_get();
1304     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1305 };
1307 NR::Point
1308 FlowtextKnotHolderEntity::knot_get()
1310     SPRect *rect = SP_RECT(item);
1312     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1315 void
1316 FlowtextKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
1318     SPRect *rect = SP_RECT(item);
1320     sp_rect_wh_set_internal(rect, p, origin, state);
1323 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1324     KnotHolder(desktop, item, relhandler)
1326     g_assert(item != NULL);
1328     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1329     entity_flowtext->create(desktop, item, this,
1330                             _("Drag to resize the <b>flowed text frame</b>"));
1331     entity.push_back(entity_flowtext);
1334 /*
1335   Local Variables:
1336   mode:c++
1337   c-file-style:"stroustrup"
1338   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1339   indent-tabs-mode:nil
1340   fill-column:99
1341   End:
1342 */
1343 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :