Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / object-edit.cpp
1 /*
2  * Node editing extension to objects
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   Mitsuru Oka
7  *   Maximilian Albert <maximilian.albert@gmail.com>
8  *   Abhishek Sharma
9  *
10  * Licensed under GNU GPL
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
19 #include "sp-item.h"
20 #include "sp-rect.h"
21 #include "box3d.h"
22 #include "sp-ellipse.h"
23 #include "sp-star.h"
24 #include "sp-spiral.h"
25 #include "sp-offset.h"
26 #include "sp-flowtext.h"
27 #include "preferences.h"
28 #include "style.h"
29 #include "desktop.h"
30 #include "desktop-handles.h"
31 #include "sp-namedview.h"
32 #include "live_effects/effect.h"
34 #include "sp-pattern.h"
35 #include "sp-path.h"
37 #include <glibmm/i18n.h>
39 #include "object-edit.h"
41 #include <libnr/nr-scale-ops.h>
43 #include "xml/repr.h"
45 #include "2geom/isnan.h"
47 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
49 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
50 {
51     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
53     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
54     effect->addHandles(knot_holder, desktop, item);
56     return knot_holder;
57 }
59 KnotHolder *
60 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
61 {
62     KnotHolder *knotholder = NULL;
64     if (SP_IS_LPE_ITEM(item) &&
65         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
66         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
67         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
68         knotholder = sp_lpe_knot_holder(item, desktop);
69     } else if (SP_IS_RECT(item)) {
70         knotholder = new RectKnotHolder(desktop, item, NULL);
71     } else if (SP_IS_BOX3D(item)) {
72         knotholder = new Box3DKnotHolder(desktop, item, NULL);
73     } else if (SP_IS_ARC(item)) {
74         knotholder = new ArcKnotHolder(desktop, item, NULL);
75     } else if (SP_IS_STAR(item)) {
76         knotholder = new StarKnotHolder(desktop, item, NULL);
77     } else if (SP_IS_SPIRAL(item)) {
78         knotholder = new SpiralKnotHolder(desktop, item, NULL);
79     } else if (SP_IS_OFFSET(item)) {
80         knotholder = new OffsetKnotHolder(desktop, item, NULL);
81     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
82         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
83     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
84                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
85         knotholder = new KnotHolder(desktop, item, NULL);
86         knotholder->add_pattern_knotholder();
87     }
89     return knotholder;
90 }
92 /* SPRect */
94 /* handle for horizontal rounding radius */
95 class RectKnotHolderEntityRX : public KnotHolderEntity {
96 public:
97     virtual Geom::Point knot_get();
98     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
99     virtual void knot_click(guint state);
100 };
102 /* handle for vertical rounding radius */
103 class RectKnotHolderEntityRY : public KnotHolderEntity {
104 public:
105     virtual Geom::Point knot_get();
106     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
107     virtual void knot_click(guint state);
108 };
110 /* handle for width/height adjustment */
111 class RectKnotHolderEntityWH : public KnotHolderEntity {
112 public:
113     virtual Geom::Point knot_get();
114     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
116 protected:
117     void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
118 };
120 /* handle for x/y adjustment */
121 class RectKnotHolderEntityXY : public KnotHolderEntity {
122 public:
123     virtual Geom::Point knot_get();
124     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
125 };
127 Geom::Point
128 RectKnotHolderEntityRX::knot_get()
130     SPRect *rect = SP_RECT(item);
132     return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
135 void
136 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
138     SPRect *rect = SP_RECT(item);
140     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
141     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
142     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
143     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
145     if (state & GDK_CONTROL_MASK) {
146         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
147         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
148         rect->rx._set = rect->ry._set = true;
150     } else {
151         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
152         rect->rx._set = true;
153     }
155     update_knot();
157     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
160 void
161 RectKnotHolderEntityRX::knot_click(guint state)
163     SPRect *rect = SP_RECT(item);
165     if (state & GDK_SHIFT_MASK) {
166         /* remove rounding from rectangle */
167         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
168         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
169     } else if (state & GDK_CONTROL_MASK) {
170         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
171         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
172     }
176 Geom::Point
177 RectKnotHolderEntityRY::knot_get()
179     SPRect *rect = SP_RECT(item);
181     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
184 void
185 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
187     SPRect *rect = SP_RECT(item);
189     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
190     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
191     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
192     Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
194     if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
195                                     // resulting in a perfect circle (and not an ellipse)
196         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
197         rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
198         rect->ry._set = rect->rx._set = true;
199     } else {
200         if (!rect->rx._set || rect->rx.computed == 0) {
201             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
202                                       0.0,
203                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
204         } else {
205             rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
206                                       0.0,
207                                       rect->height.computed / 2.0);
208         }
210         rect->ry._set = true;
211     }
213     update_knot();
215     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
218 void
219 RectKnotHolderEntityRY::knot_click(guint state)
221     SPRect *rect = SP_RECT(item);
223     if (state & GDK_SHIFT_MASK) {
224         /* remove rounding */
225         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
226         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
227     } else if (state & GDK_CONTROL_MASK) {
228         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
229         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
230     }
233 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
235 static void sp_rect_clamp_radii(SPRect *rect)
237     // clamp rounding radii so that they do not exceed width/height
238     if (2 * rect->rx.computed > rect->width.computed) {
239         rect->rx.computed = 0.5 * rect->width.computed;
240         rect->rx._set = true;
241     }
242     if (2 * rect->ry.computed > rect->height.computed) {
243         rect->ry.computed = 0.5 * rect->height.computed;
244         rect->ry._set = true;
245     }
248 Geom::Point
249 RectKnotHolderEntityWH::knot_get()
251     SPRect *rect = SP_RECT(item);
253     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
256 void
257 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
259     SPRect *rect = SP_RECT(item);
261     Geom::Point s = p;
263     if (state & GDK_CONTROL_MASK) {
264         // original width/height when drag started
265         gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
266         gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
268         //original ratio
269         gdouble ratio = (w_orig / h_orig);
271         // mouse displacement since drag started
272         gdouble minx = p[Geom::X] - origin[Geom::X];
273         gdouble miny = p[Geom::Y] - origin[Geom::Y];
275         Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
277         if (fabs(minx) > fabs(miny)) {
278             // snap to horizontal or diagonal
279             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
280                 // closer to the diagonal and in same-sign quarters, change both using ratio
281                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
282                 minx = s[Geom::X] - origin[Geom::X];
283                 miny = s[Geom::Y] - origin[Geom::Y];
284                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
285             } else {
286                 // closer to the horizontal, change only width, height is h_orig
287                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
288                 minx = s[Geom::X] - origin[Geom::X];
289                 miny = s[Geom::Y] - origin[Geom::Y];
290                 rect->height.computed = MAX(h_orig, 0);
291             }
292             rect->width.computed = MAX(w_orig + minx, 0);
294         } else {
295             // snap to vertical or diagonal
296             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
297                 // closer to the diagonal and in same-sign quarters, change both using ratio
298                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
299                 minx = s[Geom::X] - origin[Geom::X];
300                 miny = s[Geom::Y] - origin[Geom::Y];
301                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
302             } else {
303                 // closer to the vertical, change only height, width is w_orig
304                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
305                 minx = s[Geom::X] - origin[Geom::X];
306                 miny = s[Geom::Y] - origin[Geom::Y];
307                 rect->width.computed = MAX(w_orig, 0);
308             }
309             rect->height.computed = MAX(h_orig + miny, 0);
311         }
313         rect->width._set = rect->height._set = true;
315     } else {
316         // move freely
317         s = snap_knot_position(p);
318         rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
319         rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
320         rect->width._set = rect->height._set = true;
321     }
323     sp_rect_clamp_radii(rect);
325     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328 void
329 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
331     set_internal(p, origin, state);
332     update_knot();
335 Geom::Point
336 RectKnotHolderEntityXY::knot_get()
338     SPRect *rect = SP_RECT(item);
340     return Geom::Point(rect->x.computed, rect->y.computed);
343 void
344 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
346     SPRect *rect = SP_RECT(item);
348     // opposite corner (unmoved)
349     gdouble opposite_x = (rect->x.computed + rect->width.computed);
350     gdouble opposite_y = (rect->y.computed + rect->height.computed);
352     // original width/height when drag started
353     gdouble w_orig = opposite_x - origin[Geom::X];
354     gdouble h_orig = opposite_y - origin[Geom::Y];
356     Geom::Point s = p;
357     Geom::Point p_handle(rect->x.computed, rect->y.computed);
359     // mouse displacement since drag started
360     gdouble minx = p[Geom::X] - origin[Geom::X];
361     gdouble miny = p[Geom::Y] - origin[Geom::Y];
363     if (state & GDK_CONTROL_MASK) {
364         //original ratio
365         gdouble ratio = (w_orig / h_orig);
367         if (fabs(minx) > fabs(miny)) {
368             // snap to horizontal or diagonal
369             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
370                 // closer to the diagonal and in same-sign quarters, change both using ratio
371                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
372                 minx = s[Geom::X] - origin[Geom::X];
373                 miny = s[Geom::Y] - origin[Geom::Y];
374                 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
375                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
376             } else {
377                 // closer to the horizontal, change only width, height is h_orig
378                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
379                 minx = s[Geom::X] - origin[Geom::X];
380                 miny = s[Geom::Y] - origin[Geom::Y];
381                 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
382                 rect->height.computed = MAX(h_orig, 0);
383             }
384             rect->x.computed = MIN(s[Geom::X], opposite_x);
385             rect->width.computed = MAX(w_orig - minx, 0);
386         } else {
387             // snap to vertical or diagonal
388             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
389                 // closer to the diagonal and in same-sign quarters, change both using ratio
390                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
391                 minx = s[Geom::X] - origin[Geom::X];
392                 miny = s[Geom::Y] - origin[Geom::Y];
393                 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
394                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
395             } else {
396                 // closer to the vertical, change only height, width is w_orig
397                 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
398                 minx = s[Geom::X] - origin[Geom::X];
399                 miny = s[Geom::Y] - origin[Geom::Y];
400                 rect->x.computed = MIN(origin[Geom::X], opposite_x);
401                 rect->width.computed = MAX(w_orig, 0);
402             }
403             rect->y.computed = MIN(s[Geom::Y], opposite_y);
404             rect->height.computed = MAX(h_orig - miny, 0);
405         }
407         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
409     } else {
410         // move freely
411         s = snap_knot_position(p);
412         minx = s[Geom::X] - origin[Geom::X];
413         miny = s[Geom::Y] - origin[Geom::Y];
415         rect->x.computed = MIN(s[Geom::X], opposite_x);
416         rect->width.computed = MAX(w_orig - minx, 0);
417         rect->y.computed = MIN(s[Geom::Y], opposite_y);
418         rect->height.computed = MAX(h_orig - miny, 0);
419         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
420     }
422     sp_rect_clamp_radii(rect);
424     update_knot();
426     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
429 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
430     KnotHolder(desktop, item, relhandler)
432     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
433     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
434     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
435     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
436     entity_rx->create(desktop, item, this,
437                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
438                         "to make the vertical radius the same"),
439                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
440     entity_ry->create(desktop, item, this,
441                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
442                         "to make the horizontal radius the same"),
443                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
444     entity_wh->create(desktop, item, this,
445                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
446                         "to lock ratio or stretch in one dimension only"),
447                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
448     entity_xy->create(desktop, item, this,
449                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
450                         "to lock ratio or stretch in one dimension only"),
451                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
452     entity.push_back(entity_rx);
453     entity.push_back(entity_ry);
454     entity.push_back(entity_wh);
455     entity.push_back(entity_xy);
457     add_pattern_knotholder();
460 /* Box3D (= the new 3D box structure) */
462 class Box3DKnotHolderEntity : public KnotHolderEntity {
463 public:
464     virtual Geom::Point knot_get() = 0;
465     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
467     Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
468     void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
469 };
471 Geom::Point
472 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
474     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
477 void
478 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
480     Geom::Point const s = snap_knot_position(new_pos);
482     g_assert(item != NULL);
483     SPBox3D *box = SP_BOX3D(item);
484     Geom::Matrix const i2d (item->i2d_affine ());
486     Box3D::Axis movement;
487     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
488         movement = Box3D::XY;
489     } else {
490         movement = Box3D::Z;
491     }
493     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
494     box3d_set_z_orders(box);
495     box3d_position_set(box);
498 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
499 public:
500     virtual Geom::Point knot_get();
501     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
502 };
504 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
505 public:
506     virtual Geom::Point knot_get();
507     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
508 };
510 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
511 public:
512     virtual Geom::Point knot_get();
513     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
514 };
516 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
517 public:
518     virtual Geom::Point knot_get();
519     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
520 };
522 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
523 public:
524     virtual Geom::Point knot_get();
525     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
526 };
528 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
529 public:
530     virtual Geom::Point knot_get();
531     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
532 };
534 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
535 public:
536     virtual Geom::Point knot_get();
537     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
538 };
540 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
541 public:
542     virtual Geom::Point knot_get();
543     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
544 };
546 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
547 public:
548     virtual Geom::Point knot_get();
549     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
550 };
552 Geom::Point
553 Box3DKnotHolderEntity0::knot_get()
555     return knot_get_generic(item, 0);
558 Geom::Point
559 Box3DKnotHolderEntity1::knot_get()
561     return knot_get_generic(item, 1);
564 Geom::Point
565 Box3DKnotHolderEntity2::knot_get()
567     return knot_get_generic(item, 2);
570 Geom::Point
571 Box3DKnotHolderEntity3::knot_get()
573     return knot_get_generic(item, 3);
576 Geom::Point
577 Box3DKnotHolderEntity4::knot_get()
579     return knot_get_generic(item, 4);
582 Geom::Point
583 Box3DKnotHolderEntity5::knot_get()
585     return knot_get_generic(item, 5);
588 Geom::Point
589 Box3DKnotHolderEntity6::knot_get()
591     return knot_get_generic(item, 6);
594 Geom::Point
595 Box3DKnotHolderEntity7::knot_get()
597     return knot_get_generic(item, 7);
600 Geom::Point
601 Box3DKnotHolderEntityCenter::knot_get()
603     return box3d_get_center_screen(SP_BOX3D(item));
606 void
607 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
609     knot_set_generic(item, 0, new_pos, state);
612 void
613 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
615     knot_set_generic(item, 1, new_pos, state);
618 void
619 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
621     knot_set_generic(item, 2, new_pos, state);
624 void
625 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
627     knot_set_generic(item, 3, new_pos, state);
630 void
631 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
633     knot_set_generic(item, 4, new_pos, state);
636 void
637 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
639     knot_set_generic(item, 5, new_pos, state);
642 void
643 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
645     knot_set_generic(item, 6, new_pos, state);
648 void
649 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
651     knot_set_generic(item, 7, new_pos, state);
654 void
655 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
657     Geom::Point const s = snap_knot_position(new_pos);
659     SPBox3D *box = SP_BOX3D(item);
660     Geom::Matrix const i2d (item->i2d_affine ());
662     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
663                       state & GDK_CONTROL_MASK);
665     box3d_set_z_orders(box);
666     box3d_position_set(box);
669 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
670     KnotHolder(desktop, item, relhandler)
672     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
673     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
674     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
675     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
676     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
677     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
678     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
679     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
680     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
682     entity_corner0->create(desktop, item, this,
683                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
684                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
685     entity_corner1->create(desktop, item, this,
686                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
687                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
688     entity_corner2->create(desktop, item, this,
689                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
690                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
691     entity_corner3->create(desktop, item, this,
692                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
693                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
694     entity_corner4->create(desktop, item, this,
695                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
696                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
697     entity_corner5->create(desktop, item, this,
698                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
699                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
700     entity_corner6->create(desktop, item, this,
701                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
702                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
703     entity_corner7->create(desktop, item, this,
704                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
705                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
706     entity_center->create(desktop, item, this,
707                           _("Move the box in perspective"),
708                           SP_KNOT_SHAPE_CROSS);
710     entity.push_back(entity_corner0);
711     entity.push_back(entity_corner1);
712     entity.push_back(entity_corner2);
713     entity.push_back(entity_corner3);
714     entity.push_back(entity_corner4);
715     entity.push_back(entity_corner5);
716     entity.push_back(entity_corner6);
717     entity.push_back(entity_corner7);
718     entity.push_back(entity_center);
720     add_pattern_knotholder();
723 /* SPArc */
725 class ArcKnotHolderEntityStart : public KnotHolderEntity {
726 public:
727     virtual Geom::Point knot_get();
728     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
729 };
731 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
732 public:
733     virtual Geom::Point knot_get();
734     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
735     virtual void knot_click(guint state);
736 };
738 class ArcKnotHolderEntityRX : public KnotHolderEntity {
739 public:
740     virtual Geom::Point knot_get();
741     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
742     virtual void knot_click(guint state);
743 };
745 class ArcKnotHolderEntityRY : public KnotHolderEntity {
746 public:
747     virtual Geom::Point knot_get();
748     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
749     virtual void knot_click(guint state);
750 };
752 /*
753  * return values:
754  *   1  : inside
755  *   0  : on the curves
756  *   -1 : outside
757  */
758 static gint
759 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
761     gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
762     gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
764     gdouble s = dx * dx + dy * dy;
765     if (s < 1.0) return 1;
766     if (s > 1.0) return -1;
767     return 0;
770 void
771 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
773     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
774     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
776     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
777     SPArc *arc = SP_ARC(item);
779     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
781     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
782     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
783     ge->start = atan2(delta * sc.inverse());
784     if ( ( state & GDK_CONTROL_MASK )
785          && snaps )
786     {
787         ge->start = sp_round(ge->start, M_PI/snaps);
788     }
789     sp_genericellipse_normalize(ge);
790     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
793 Geom::Point
794 ArcKnotHolderEntityStart::knot_get()
796     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
797     SPArc *arc = SP_ARC(item);
799     return sp_arc_get_xy(arc, ge->start);
802 void
803 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
805     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
806     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
808     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
809     SPArc *arc = SP_ARC(item);
811     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
813     Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
814     Geom::Scale sc(ge->rx.computed, ge->ry.computed);
815     ge->end = atan2(delta * sc.inverse());
816     if ( ( state & GDK_CONTROL_MASK )
817          && snaps )
818     {
819         ge->end = sp_round(ge->end, M_PI/snaps);
820     }
821     sp_genericellipse_normalize(ge);
822     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
825 Geom::Point
826 ArcKnotHolderEntityEnd::knot_get()
828     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
829     SPArc *arc = SP_ARC(item);
831     return sp_arc_get_xy(arc, ge->end);
835 void
836 ArcKnotHolderEntityEnd::knot_click(guint state)
838     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
840     if (state & GDK_SHIFT_MASK) {
841         ge->end = ge->start = 0;
842         ((SPObject *)ge)->updateRepr();
843     }
847 void
848 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
850     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
852     Geom::Point const s = snap_knot_position(p);
854     ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
856     if ( state & GDK_CONTROL_MASK ) {
857         ge->ry.computed = ge->rx.computed;
858     }
860     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
863 Geom::Point
864 ArcKnotHolderEntityRX::knot_get()
866     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
868     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(ge->rx.computed, 0));
871 void
872 ArcKnotHolderEntityRX::knot_click(guint state)
874     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
876     if (state & GDK_CONTROL_MASK) {
877         ge->ry.computed = ge->rx.computed;
878         ((SPObject *)ge)->updateRepr();
879     }
882 void
883 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
885     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
887     Geom::Point const s = snap_knot_position(p);
889     ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
891     if ( state & GDK_CONTROL_MASK ) {
892         ge->rx.computed = ge->ry.computed;
893     }
895     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
898 Geom::Point
899 ArcKnotHolderEntityRY::knot_get()
901     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
903     return (Geom::Point(ge->cx.computed, ge->cy.computed) -  Geom::Point(0, ge->ry.computed));
906 void
907 ArcKnotHolderEntityRY::knot_click(guint state)
909     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
911     if (state & GDK_CONTROL_MASK) {
912         ge->rx.computed = ge->ry.computed;
913         ((SPObject *)ge)->updateRepr();
914     }
917 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
918     KnotHolder(desktop, item, relhandler)
920     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
921     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
922     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
923     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
924     entity_rx->create(desktop, item, this,
925                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
926                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
927     entity_ry->create(desktop, item, this,
928                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
929                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
930     entity_start->create(desktop, item, this,
931                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> "
932                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
933                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
934     entity_end->create(desktop, item, this,
935                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
936                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
937                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
938     entity.push_back(entity_rx);
939     entity.push_back(entity_ry);
940     entity.push_back(entity_start);
941     entity.push_back(entity_end);
943     add_pattern_knotholder();
946 /* SPStar */
948 class StarKnotHolderEntity1 : public KnotHolderEntity {
949 public:
950     virtual Geom::Point knot_get();
951     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
952     virtual void knot_click(guint state);
953 };
955 class StarKnotHolderEntity2 : public KnotHolderEntity {
956 public:
957     virtual Geom::Point knot_get();
958     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
959     virtual void knot_click(guint state);
960 };
962 void
963 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
965     SPStar *star = SP_STAR(item);
967     Geom::Point const s = snap_knot_position(p);
969     Geom::Point d = s - to_2geom(star->center);
971     double arg1 = atan2(d);
972     double darg1 = arg1 - star->arg[0];
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 = darg1/(star->arg[0] - star->arg[1]);
978     } else if (state & GDK_CONTROL_MASK) {
979         star->r[0]    = L2(d);
980     } else {
981         star->r[0]    = L2(d);
982         star->arg[0]  = arg1;
983         star->arg[1] += darg1;
984     }
985     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
988 void
989 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
991     SPStar *star = SP_STAR(item);
993     Geom::Point const s = snap_knot_position(p);
995     if (star->flatsided == false) {
996         Geom::Point d = s - to_2geom(star->center);
998         double arg1 = atan2(d);
999         double darg1 = arg1 - star->arg[1];
1001         if (state & GDK_MOD1_MASK) {
1002             star->randomized = darg1/(star->arg[0] - star->arg[1]);
1003         } else if (state & GDK_SHIFT_MASK) {
1004             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1005         } else if (state & GDK_CONTROL_MASK) {
1006             star->r[1]   = L2(d);
1007             star->arg[1] = star->arg[0] + M_PI / star->sides;
1008         }
1009         else {
1010             star->r[1]   = L2(d);
1011             star->arg[1] = atan2(d);
1012         }
1013         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1014     }
1017 Geom::Point
1018 StarKnotHolderEntity1::knot_get()
1020     g_assert(item != NULL);
1022     SPStar *star = SP_STAR(item);
1024     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1028 Geom::Point
1029 StarKnotHolderEntity2::knot_get()
1031     g_assert(item != NULL);
1033     SPStar *star = SP_STAR(item);
1035     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1038 static void
1039 sp_star_knot_click(SPItem *item, guint state)
1041     SPStar *star = SP_STAR(item);
1043     if (state & GDK_MOD1_MASK) {
1044         star->randomized = 0;
1045         ((SPObject *)star)->updateRepr();
1046     } else if (state & GDK_SHIFT_MASK) {
1047         star->rounded = 0;
1048         ((SPObject *)star)->updateRepr();
1049     } else if (state & GDK_CONTROL_MASK) {
1050         star->arg[1] = star->arg[0] + M_PI / star->sides;
1051         ((SPObject *)star)->updateRepr();
1052     }
1055 void
1056 StarKnotHolderEntity1::knot_click(guint state)
1058     return sp_star_knot_click(item, state);
1061 void
1062 StarKnotHolderEntity2::knot_click(guint state)
1064     return sp_star_knot_click(item, state);
1067 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1068     KnotHolder(desktop, item, relhandler)
1070     SPStar *star = SP_STAR(item);
1072     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1073     entity1->create(desktop, item, this,
1074                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1075                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1076     entity.push_back(entity1);
1078     if (star->flatsided == false) {
1079         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1080         entity2->create(desktop, item, this,
1081                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1082                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1083         entity.push_back(entity2);
1084     }
1086     add_pattern_knotholder();
1089 /* SPSpiral */
1091 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1092 public:
1093     virtual Geom::Point knot_get();
1094     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1095     virtual void knot_click(guint state);
1096 };
1098 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1099 public:
1100     virtual Geom::Point knot_get();
1101     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1102 };
1105 /*
1106  * set attributes via inner (t=t0) knot point:
1107  *   [default] increase/decrease inner point
1108  *   [shift]   increase/decrease inner and outer arg synchronizely
1109  *   [control] constrain inner arg to round per PI/4
1110  */
1111 void
1112 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1114     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1115     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1117     SPSpiral *spiral = SP_SPIRAL(item);
1119     gdouble   dx = p[Geom::X] - spiral->cx;
1120     gdouble   dy = p[Geom::Y] - spiral->cy;
1122     gdouble   moved_y = p[Geom::Y] - origin[Geom::Y];
1124     if (state & GDK_MOD1_MASK) {
1125         // adjust divergence by vertical drag, relative to rad
1126         if (spiral->rad > 0) {
1127             double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down
1128             spiral->exp += exp_delta;
1129             if (spiral->exp < 1e-3)
1130                 spiral->exp = 1e-3;
1131         }
1132     } else {
1133         // roll/unroll from inside
1134         gdouble   arg_t0;
1135         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1137         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1138         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1139         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1141         /* round inner arg per PI/snaps, if CTRL is pressed */
1142         if ( ( state & GDK_CONTROL_MASK )
1143              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1144              && ( snaps != 0 ) ) {
1145             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1146             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1147         }
1149         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1150     }
1152     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1155 /*
1156  * set attributes via outer (t=1) knot point:
1157  *   [default] increase/decrease revolution factor
1158  *   [control] constrain inner arg to round per PI/4
1159  */
1160 void
1161 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1163     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1164     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1166     SPSpiral *spiral = SP_SPIRAL(item);
1168     gdouble  dx = p[Geom::X] - spiral->cx;
1169     gdouble  dy = p[Geom::Y] - spiral->cy;
1171     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1172         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1173         if (!(state & GDK_MOD1_MASK)) {
1174             // if alt not pressed, change also rad; otherwise it is locked
1175             spiral->rad = MAX(hypot(dx, dy), 0.001);
1176         }
1177         if ( ( state & GDK_CONTROL_MASK )
1178              && snaps ) {
1179             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1180         }
1181     } else { // roll/unroll
1182         // arg of the spiral outer end
1183         double arg_1;
1184         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1186         // its fractional part after the whole turns are subtracted
1187         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1189         // arg of the mouse point relative to spiral center
1190         double mouse_angle = atan2(dy, dx);
1191         if (mouse_angle < 0)
1192             mouse_angle += 2*M_PI;
1194         // snap if ctrl
1195         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1196             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1197         }
1199         // by how much we want to rotate the outer point
1200         double diff = mouse_angle - arg_r;
1201         if (diff > M_PI)
1202             diff -= 2*M_PI;
1203         else if (diff < -M_PI)
1204             diff += 2*M_PI;
1206         // calculate the new rad;
1207         // the value of t corresponding to the angle arg_1 + diff:
1208         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1209         // the rad at that t:
1210         double rad_new = 0;
1211         if (t_temp > spiral->t0)
1212             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1214         // change the revo (converting diff from radians to the number of turns)
1215         spiral->revo += diff/(2*M_PI);
1216         if (spiral->revo < 1e-3)
1217             spiral->revo = 1e-3;
1219         // if alt not pressed and the values are sane, change the rad
1220         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1221             // adjust t0 too so that the inner point stays unmoved
1222             double r0;
1223             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1224             spiral->rad = rad_new;
1225             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1226         }
1227         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1228         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1229     }
1231     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1234 Geom::Point
1235 SpiralKnotHolderEntityInner::knot_get()
1237     SPSpiral *spiral = SP_SPIRAL(item);
1239     return sp_spiral_get_xy(spiral, spiral->t0);
1242 Geom::Point
1243 SpiralKnotHolderEntityOuter::knot_get()
1245     SPSpiral *spiral = SP_SPIRAL(item);
1247     return sp_spiral_get_xy(spiral, 1.0);
1250 void
1251 SpiralKnotHolderEntityInner::knot_click(guint state)
1253     SPSpiral *spiral = SP_SPIRAL(item);
1255     if (state & GDK_MOD1_MASK) {
1256         spiral->exp = 1;
1257         ((SPObject *)spiral)->updateRepr();
1258     } else if (state & GDK_SHIFT_MASK) {
1259         spiral->t0 = 0;
1260         ((SPObject *)spiral)->updateRepr();
1261     }
1264 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1265     KnotHolder(desktop, item, relhandler)
1267     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1268     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1269     entity_inner->create(desktop, item, this,
1270                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1271                            "with <b>Alt</b> to converge/diverge"));
1272     entity_outer->create(desktop, item, this,
1273                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1274                            "with <b>Shift</b> to scale/rotate"));
1275     entity.push_back(entity_inner);
1276     entity.push_back(entity_outer);
1278     add_pattern_knotholder();
1281 /* SPOffset */
1283 class OffsetKnotHolderEntity : public KnotHolderEntity {
1284 public:
1285     virtual Geom::Point knot_get();
1286     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1287 };
1289 void
1290 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1292     SPOffset *offset = SP_OFFSET(item);
1294     offset->rad = sp_offset_distance_to_original(offset, p);
1295     offset->knot = p;
1296     offset->knotSet = true;
1298     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1302 Geom::Point
1303 OffsetKnotHolderEntity::knot_get()
1305     SPOffset *offset = SP_OFFSET(item);
1307     Geom::Point np;
1308     sp_offset_top_point(offset,&np);
1309     return np;
1312 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1313     KnotHolder(desktop, item, relhandler)
1315     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1316     entity_offset->create(desktop, item, this,
1317                           _("Adjust the <b>offset distance</b>"));
1318     entity.push_back(entity_offset);
1320     add_pattern_knotholder();
1323 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1324 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1325 // sense logically.
1326 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1327 public:
1328     virtual Geom::Point knot_get();
1329     virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1330 };
1332 Geom::Point
1333 FlowtextKnotHolderEntity::knot_get()
1335     SPRect *rect = SP_RECT(item);
1337     return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1340 void
1341 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1343     set_internal(p, origin, state);
1346 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1347     KnotHolder(desktop, item, relhandler)
1349     g_assert(item != NULL);
1351     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1352     entity_flowtext->create(desktop, item, this,
1353                             _("Drag to resize the <b>flowed text frame</b>"));
1354     entity.push_back(entity_flowtext);
1357 /*
1358   Local Variables:
1359   mode:c++
1360   c-file-style:"stroustrup"
1361   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1362   indent-tabs-mode:nil
1363   fill-column:99
1364   End:
1365 */
1366 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :