1 #define __SP_OBJECT_EDIT_C__
3 /*
4 * Node editing extension to objects
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Mitsuru Oka
9 * Maximilian Albert <maximilian.albert@gmail.com>
10 *
11 * Licensed under GNU GPL
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
20 #include "sp-item.h"
21 #include "sp-rect.h"
22 #include "box3d.h"
23 #include "sp-ellipse.h"
24 #include "sp-star.h"
25 #include "sp-spiral.h"
26 #include "sp-offset.h"
27 #include "sp-flowtext.h"
28 #include "preferences.h"
29 #include "style.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "sp-namedview.h"
33 #include "live_effects/effect.h"
35 #include "sp-pattern.h"
36 #include "sp-path.h"
38 #include <glibmm/i18n.h>
40 #include "object-edit.h"
42 #include <libnr/nr-scale-ops.h>
44 #include "xml/repr.h"
46 #include "2geom/isnan.h"
48 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
50 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
51 {
52 KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
54 Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
55 effect->addHandles(knot_holder, desktop, item);
57 return knot_holder;
58 }
60 KnotHolder *
61 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
62 {
63 KnotHolder *knotholder = NULL;
65 if (SP_IS_LPE_ITEM(item) &&
66 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
67 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
68 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
69 knotholder = sp_lpe_knot_holder(item, desktop);
70 } else if (SP_IS_RECT(item)) {
71 knotholder = new RectKnotHolder(desktop, item, NULL);
72 } else if (SP_IS_BOX3D(item)) {
73 knotholder = new Box3DKnotHolder(desktop, item, NULL);
74 } else if (SP_IS_ARC(item)) {
75 knotholder = new ArcKnotHolder(desktop, item, NULL);
76 } else if (SP_IS_STAR(item)) {
77 knotholder = new StarKnotHolder(desktop, item, NULL);
78 } else if (SP_IS_SPIRAL(item)) {
79 knotholder = new SpiralKnotHolder(desktop, item, NULL);
80 } else if (SP_IS_OFFSET(item)) {
81 knotholder = new OffsetKnotHolder(desktop, item, NULL);
82 } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
83 knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
84 } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
85 && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
86 knotholder = new KnotHolder(desktop, item, NULL);
87 knotholder->add_pattern_knotholder();
88 }
90 return knotholder;
91 }
93 /* SPRect */
95 /* handle for horizontal rounding radius */
96 class RectKnotHolderEntityRX : public KnotHolderEntity {
97 public:
98 virtual Geom::Point knot_get();
99 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
100 virtual void knot_click(guint state);
101 };
103 /* handle for vertical rounding radius */
104 class RectKnotHolderEntityRY : public KnotHolderEntity {
105 public:
106 virtual Geom::Point knot_get();
107 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
108 virtual void knot_click(guint state);
109 };
111 /* handle for width/height adjustment */
112 class RectKnotHolderEntityWH : public KnotHolderEntity {
113 public:
114 virtual Geom::Point knot_get();
115 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
117 protected:
118 void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
119 };
121 /* handle for x/y adjustment */
122 class RectKnotHolderEntityXY : public KnotHolderEntity {
123 public:
124 virtual Geom::Point knot_get();
125 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
126 };
128 Geom::Point
129 RectKnotHolderEntityRX::knot_get()
130 {
131 SPRect *rect = SP_RECT(item);
133 return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
134 }
136 void
137 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
138 {
139 SPRect *rect = SP_RECT(item);
141 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
142 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
143 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
144 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
146 if (state & GDK_CONTROL_MASK) {
147 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
148 rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
149 rect->rx._set = rect->ry._set = true;
151 } else {
152 rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
153 rect->rx._set = true;
154 }
156 update_knot();
158 ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159 }
161 void
162 RectKnotHolderEntityRX::knot_click(guint state)
163 {
164 SPRect *rect = SP_RECT(item);
166 if (state & GDK_SHIFT_MASK) {
167 /* remove rounding from rectangle */
168 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
169 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
170 } else if (state & GDK_CONTROL_MASK) {
171 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
172 SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
173 }
175 }
177 Geom::Point
178 RectKnotHolderEntityRY::knot_get()
179 {
180 SPRect *rect = SP_RECT(item);
182 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
183 }
185 void
186 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
187 {
188 SPRect *rect = SP_RECT(item);
190 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
191 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
192 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
193 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
195 if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
196 // resulting in a perfect circle (and not an ellipse)
197 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
198 rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
199 rect->ry._set = rect->rx._set = true;
200 } else {
201 if (!rect->rx._set || rect->rx.computed == 0) {
202 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
203 0.0,
204 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
205 } else {
206 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
207 0.0,
208 rect->height.computed / 2.0);
209 }
211 rect->ry._set = true;
212 }
214 update_knot();
216 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
217 }
219 void
220 RectKnotHolderEntityRY::knot_click(guint state)
221 {
222 SPRect *rect = SP_RECT(item);
224 if (state & GDK_SHIFT_MASK) {
225 /* remove rounding */
226 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
227 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
228 } else if (state & GDK_CONTROL_MASK) {
229 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
230 SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
231 }
232 }
234 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
236 static void sp_rect_clamp_radii(SPRect *rect)
237 {
238 // clamp rounding radii so that they do not exceed width/height
239 if (2 * rect->rx.computed > rect->width.computed) {
240 rect->rx.computed = 0.5 * rect->width.computed;
241 rect->rx._set = true;
242 }
243 if (2 * rect->ry.computed > rect->height.computed) {
244 rect->ry.computed = 0.5 * rect->height.computed;
245 rect->ry._set = true;
246 }
247 }
249 Geom::Point
250 RectKnotHolderEntityWH::knot_get()
251 {
252 SPRect *rect = SP_RECT(item);
254 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
255 }
257 void
258 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
259 {
260 SPRect *rect = SP_RECT(item);
262 Geom::Point s = p;
264 if (state & GDK_CONTROL_MASK) {
265 // original width/height when drag started
266 gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
267 gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
269 //original ratio
270 gdouble ratio = (w_orig / h_orig);
272 // mouse displacement since drag started
273 gdouble minx = p[Geom::X] - origin[Geom::X];
274 gdouble miny = p[Geom::Y] - origin[Geom::Y];
276 Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
278 if (fabs(minx) > fabs(miny)) {
279 // snap to horizontal or diagonal
280 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
281 // closer to the diagonal and in same-sign quarters, change both using ratio
282 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
283 minx = s[Geom::X] - origin[Geom::X];
284 miny = s[Geom::Y] - origin[Geom::Y];
285 rect->height.computed = MAX(h_orig + minx / ratio, 0);
286 } else {
287 // closer to the horizontal, change only width, height is h_orig
288 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
289 minx = s[Geom::X] - origin[Geom::X];
290 miny = s[Geom::Y] - origin[Geom::Y];
291 rect->height.computed = MAX(h_orig, 0);
292 }
293 rect->width.computed = MAX(w_orig + minx, 0);
295 } else {
296 // snap to vertical or diagonal
297 if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
298 // closer to the diagonal and in same-sign quarters, change both using ratio
299 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
300 minx = s[Geom::X] - origin[Geom::X];
301 miny = s[Geom::Y] - origin[Geom::Y];
302 rect->width.computed = MAX(w_orig + miny * ratio, 0);
303 } else {
304 // closer to the vertical, change only height, width is w_orig
305 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
306 minx = s[Geom::X] - origin[Geom::X];
307 miny = s[Geom::Y] - origin[Geom::Y];
308 rect->width.computed = MAX(w_orig, 0);
309 }
310 rect->height.computed = MAX(h_orig + miny, 0);
312 }
314 rect->width._set = rect->height._set = true;
316 } else {
317 // move freely
318 s = snap_knot_position(p);
319 rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
320 rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
321 rect->width._set = rect->height._set = true;
322 }
324 sp_rect_clamp_radii(rect);
326 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327 }
329 void
330 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
331 {
332 set_internal(p, origin, state);
333 update_knot();
334 }
336 Geom::Point
337 RectKnotHolderEntityXY::knot_get()
338 {
339 SPRect *rect = SP_RECT(item);
341 return Geom::Point(rect->x.computed, rect->y.computed);
342 }
344 void
345 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
346 {
347 SPRect *rect = SP_RECT(item);
349 // opposite corner (unmoved)
350 gdouble opposite_x = (rect->x.computed + rect->width.computed);
351 gdouble opposite_y = (rect->y.computed + rect->height.computed);
353 // original width/height when drag started
354 gdouble w_orig = opposite_x - origin[Geom::X];
355 gdouble h_orig = opposite_y - origin[Geom::Y];
357 Geom::Point s = p;
358 Geom::Point p_handle(rect->x.computed, rect->y.computed);
360 // mouse displacement since drag started
361 gdouble minx = p[Geom::X] - origin[Geom::X];
362 gdouble miny = p[Geom::Y] - origin[Geom::Y];
364 if (state & GDK_CONTROL_MASK) {
365 //original ratio
366 gdouble ratio = (w_orig / h_orig);
368 if (fabs(minx) > fabs(miny)) {
369 // snap to horizontal or diagonal
370 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
371 // closer to the diagonal and in same-sign quarters, change both using ratio
372 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
373 minx = s[Geom::X] - origin[Geom::X];
374 miny = s[Geom::Y] - origin[Geom::Y];
375 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
376 rect->height.computed = MAX(h_orig - minx / ratio, 0);
377 } else {
378 // closer to the horizontal, change only width, height is h_orig
379 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)));
380 minx = s[Geom::X] - origin[Geom::X];
381 miny = s[Geom::Y] - origin[Geom::Y];
382 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
383 rect->height.computed = MAX(h_orig, 0);
384 }
385 rect->x.computed = MIN(s[Geom::X], opposite_x);
386 rect->width.computed = MAX(w_orig - minx, 0);
387 } else {
388 // snap to vertical or diagonal
389 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
390 // closer to the diagonal and in same-sign quarters, change both using ratio
391 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)));
392 minx = s[Geom::X] - origin[Geom::X];
393 miny = s[Geom::Y] - origin[Geom::Y];
394 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
395 rect->width.computed = MAX(w_orig - miny * ratio, 0);
396 } else {
397 // closer to the vertical, change only height, width is w_orig
398 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)));
399 minx = s[Geom::X] - origin[Geom::X];
400 miny = s[Geom::Y] - origin[Geom::Y];
401 rect->x.computed = MIN(origin[Geom::X], opposite_x);
402 rect->width.computed = MAX(w_orig, 0);
403 }
404 rect->y.computed = MIN(s[Geom::Y], opposite_y);
405 rect->height.computed = MAX(h_orig - miny, 0);
406 }
408 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
410 } else {
411 // move freely
412 s = snap_knot_position(p);
413 minx = s[Geom::X] - origin[Geom::X];
414 miny = s[Geom::Y] - origin[Geom::Y];
416 rect->x.computed = MIN(s[Geom::X], opposite_x);
417 rect->width.computed = MAX(w_orig - minx, 0);
418 rect->y.computed = MIN(s[Geom::Y], opposite_y);
419 rect->height.computed = MAX(h_orig - miny, 0);
420 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
421 }
423 sp_rect_clamp_radii(rect);
425 update_knot();
427 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
428 }
430 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
431 KnotHolder(desktop, item, relhandler)
432 {
433 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
434 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
435 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
436 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
437 entity_rx->create(desktop, item, this,
438 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
439 "to make the vertical radius the same"),
440 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
441 entity_ry->create(desktop, item, this,
442 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
443 "to make the horizontal radius the same"),
444 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
445 entity_wh->create(desktop, item, this,
446 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
447 "to lock ratio or stretch in one dimension only"),
448 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
449 entity_xy->create(desktop, item, this,
450 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
451 "to lock ratio or stretch in one dimension only"),
452 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
453 entity.push_back(entity_rx);
454 entity.push_back(entity_ry);
455 entity.push_back(entity_wh);
456 entity.push_back(entity_xy);
458 add_pattern_knotholder();
459 }
461 /* Box3D (= the new 3D box structure) */
463 class Box3DKnotHolderEntity : public KnotHolderEntity {
464 public:
465 virtual Geom::Point knot_get() = 0;
466 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
468 Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
469 void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
470 };
472 Geom::Point
473 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
474 {
475 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
476 }
478 void
479 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
480 {
481 Geom::Point const s = snap_knot_position(new_pos);
483 g_assert(item != NULL);
484 SPBox3D *box = SP_BOX3D(item);
485 Geom::Matrix const i2d (sp_item_i2d_affine (item));
487 Box3D::Axis movement;
488 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
489 movement = Box3D::XY;
490 } else {
491 movement = Box3D::Z;
492 }
494 box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
495 box3d_set_z_orders(box);
496 box3d_position_set(box);
497 }
499 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
500 public:
501 virtual Geom::Point knot_get();
502 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
503 };
505 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
506 public:
507 virtual Geom::Point knot_get();
508 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
509 };
511 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
512 public:
513 virtual Geom::Point knot_get();
514 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
515 };
517 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
518 public:
519 virtual Geom::Point knot_get();
520 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
521 };
523 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
524 public:
525 virtual Geom::Point knot_get();
526 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
527 };
529 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
530 public:
531 virtual Geom::Point knot_get();
532 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
533 };
535 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
536 public:
537 virtual Geom::Point knot_get();
538 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
539 };
541 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
542 public:
543 virtual Geom::Point knot_get();
544 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
545 };
547 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
548 public:
549 virtual Geom::Point knot_get();
550 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
551 };
553 Geom::Point
554 Box3DKnotHolderEntity0::knot_get()
555 {
556 return knot_get_generic(item, 0);
557 }
559 Geom::Point
560 Box3DKnotHolderEntity1::knot_get()
561 {
562 return knot_get_generic(item, 1);
563 }
565 Geom::Point
566 Box3DKnotHolderEntity2::knot_get()
567 {
568 return knot_get_generic(item, 2);
569 }
571 Geom::Point
572 Box3DKnotHolderEntity3::knot_get()
573 {
574 return knot_get_generic(item, 3);
575 }
577 Geom::Point
578 Box3DKnotHolderEntity4::knot_get()
579 {
580 return knot_get_generic(item, 4);
581 }
583 Geom::Point
584 Box3DKnotHolderEntity5::knot_get()
585 {
586 return knot_get_generic(item, 5);
587 }
589 Geom::Point
590 Box3DKnotHolderEntity6::knot_get()
591 {
592 return knot_get_generic(item, 6);
593 }
595 Geom::Point
596 Box3DKnotHolderEntity7::knot_get()
597 {
598 return knot_get_generic(item, 7);
599 }
601 Geom::Point
602 Box3DKnotHolderEntityCenter::knot_get()
603 {
604 return box3d_get_center_screen(SP_BOX3D(item));
605 }
607 void
608 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
609 {
610 knot_set_generic(item, 0, new_pos, state);
611 }
613 void
614 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
615 {
616 knot_set_generic(item, 1, new_pos, state);
617 }
619 void
620 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
621 {
622 knot_set_generic(item, 2, new_pos, state);
623 }
625 void
626 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
627 {
628 knot_set_generic(item, 3, new_pos, state);
629 }
631 void
632 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
633 {
634 knot_set_generic(item, 4, new_pos, state);
635 }
637 void
638 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
639 {
640 knot_set_generic(item, 5, new_pos, state);
641 }
643 void
644 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
645 {
646 knot_set_generic(item, 6, new_pos, state);
647 }
649 void
650 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
651 {
652 knot_set_generic(item, 7, new_pos, state);
653 }
655 void
656 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
657 {
658 Geom::Point const s = snap_knot_position(new_pos);
660 SPBox3D *box = SP_BOX3D(item);
661 Geom::Matrix const i2d (sp_item_i2d_affine (item));
663 box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
664 state & GDK_CONTROL_MASK);
666 box3d_set_z_orders(box);
667 box3d_position_set(box);
668 }
670 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
671 KnotHolder(desktop, item, relhandler)
672 {
673 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
674 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
675 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
676 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
677 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
678 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
679 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
680 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
681 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
683 entity_corner0->create(desktop, item, this,
684 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
685 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
686 entity_corner1->create(desktop, item, this,
687 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
688 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
689 entity_corner2->create(desktop, item, this,
690 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
691 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
692 entity_corner3->create(desktop, item, this,
693 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
694 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
695 entity_corner4->create(desktop, item, this,
696 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
697 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
698 entity_corner5->create(desktop, item, this,
699 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
700 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
701 entity_corner6->create(desktop, item, this,
702 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
703 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
704 entity_corner7->create(desktop, item, this,
705 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
706 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
707 entity_center->create(desktop, item, this,
708 _("Move the box in perspective"),
709 SP_KNOT_SHAPE_CROSS);
711 entity.push_back(entity_corner0);
712 entity.push_back(entity_corner1);
713 entity.push_back(entity_corner2);
714 entity.push_back(entity_corner3);
715 entity.push_back(entity_corner4);
716 entity.push_back(entity_corner5);
717 entity.push_back(entity_corner6);
718 entity.push_back(entity_corner7);
719 entity.push_back(entity_center);
721 add_pattern_knotholder();
722 }
724 /* SPArc */
726 class ArcKnotHolderEntityStart : public KnotHolderEntity {
727 public:
728 virtual Geom::Point knot_get();
729 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
730 };
732 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
733 public:
734 virtual Geom::Point knot_get();
735 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
736 virtual void knot_click(guint state);
737 };
739 class ArcKnotHolderEntityRX : public KnotHolderEntity {
740 public:
741 virtual Geom::Point knot_get();
742 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
743 virtual void knot_click(guint state);
744 };
746 class ArcKnotHolderEntityRY : public KnotHolderEntity {
747 public:
748 virtual Geom::Point knot_get();
749 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
750 virtual void knot_click(guint state);
751 };
753 /*
754 * return values:
755 * 1 : inside
756 * 0 : on the curves
757 * -1 : outside
758 */
759 static gint
760 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
761 {
762 gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
763 gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
765 gdouble s = dx * dx + dy * dy;
766 if (s < 1.0) return 1;
767 if (s > 1.0) return -1;
768 return 0;
769 }
771 void
772 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
773 {
774 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
775 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
777 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
778 SPArc *arc = SP_ARC(item);
780 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
782 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
783 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
784 ge->start = atan2(delta * sc.inverse());
785 if ( ( state & GDK_CONTROL_MASK )
786 && snaps )
787 {
788 ge->start = sp_round(ge->start, M_PI/snaps);
789 }
790 sp_genericellipse_normalize(ge);
791 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
792 }
794 Geom::Point
795 ArcKnotHolderEntityStart::knot_get()
796 {
797 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
798 SPArc *arc = SP_ARC(item);
800 return sp_arc_get_xy(arc, ge->start);
801 }
803 void
804 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
805 {
806 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
807 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
809 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
810 SPArc *arc = SP_ARC(item);
812 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
814 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
815 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
816 ge->end = atan2(delta * sc.inverse());
817 if ( ( state & GDK_CONTROL_MASK )
818 && snaps )
819 {
820 ge->end = sp_round(ge->end, M_PI/snaps);
821 }
822 sp_genericellipse_normalize(ge);
823 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
824 }
826 Geom::Point
827 ArcKnotHolderEntityEnd::knot_get()
828 {
829 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
830 SPArc *arc = SP_ARC(item);
832 return sp_arc_get_xy(arc, ge->end);
833 }
836 void
837 ArcKnotHolderEntityEnd::knot_click(guint state)
838 {
839 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
841 if (state & GDK_SHIFT_MASK) {
842 ge->end = ge->start = 0;
843 ((SPObject *)ge)->updateRepr();
844 }
845 }
848 void
849 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
850 {
851 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
853 Geom::Point const s = snap_knot_position(p);
855 ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
857 if ( state & GDK_CONTROL_MASK ) {
858 ge->ry.computed = ge->rx.computed;
859 }
861 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
862 }
864 Geom::Point
865 ArcKnotHolderEntityRX::knot_get()
866 {
867 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
869 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0));
870 }
872 void
873 ArcKnotHolderEntityRX::knot_click(guint state)
874 {
875 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
877 if (state & GDK_CONTROL_MASK) {
878 ge->ry.computed = ge->rx.computed;
879 ((SPObject *)ge)->updateRepr();
880 }
881 }
883 void
884 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
885 {
886 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
888 Geom::Point const s = snap_knot_position(p);
890 ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
892 if ( state & GDK_CONTROL_MASK ) {
893 ge->rx.computed = ge->ry.computed;
894 }
896 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
897 }
899 Geom::Point
900 ArcKnotHolderEntityRY::knot_get()
901 {
902 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
904 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed));
905 }
907 void
908 ArcKnotHolderEntityRY::knot_click(guint state)
909 {
910 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
912 if (state & GDK_CONTROL_MASK) {
913 ge->rx.computed = ge->ry.computed;
914 ((SPObject *)ge)->updateRepr();
915 }
916 }
918 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
919 KnotHolder(desktop, item, relhandler)
920 {
921 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
922 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
923 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
924 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
925 entity_rx->create(desktop, item, this,
926 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
927 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
928 entity_ry->create(desktop, item, this,
929 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
930 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
931 entity_start->create(desktop, item, this,
932 _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> "
933 "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
934 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
935 entity_end->create(desktop, item, this,
936 _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
937 "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
938 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
939 entity.push_back(entity_rx);
940 entity.push_back(entity_ry);
941 entity.push_back(entity_start);
942 entity.push_back(entity_end);
944 add_pattern_knotholder();
945 }
947 /* SPStar */
949 class StarKnotHolderEntity1 : public KnotHolderEntity {
950 public:
951 virtual Geom::Point knot_get();
952 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
953 virtual void knot_click(guint state);
954 };
956 class StarKnotHolderEntity2 : public KnotHolderEntity {
957 public:
958 virtual Geom::Point knot_get();
959 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
960 virtual void knot_click(guint state);
961 };
963 void
964 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
965 {
966 SPStar *star = SP_STAR(item);
968 Geom::Point const s = snap_knot_position(p);
970 Geom::Point d = s - to_2geom(star->center);
972 double arg1 = atan2(d);
973 double darg1 = arg1 - star->arg[0];
975 if (state & GDK_MOD1_MASK) {
976 star->randomized = darg1/(star->arg[0] - star->arg[1]);
977 } else if (state & GDK_SHIFT_MASK) {
978 star->rounded = darg1/(star->arg[0] - star->arg[1]);
979 } else if (state & GDK_CONTROL_MASK) {
980 star->r[0] = L2(d);
981 } else {
982 star->r[0] = L2(d);
983 star->arg[0] = arg1;
984 star->arg[1] += darg1;
985 }
986 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
987 }
989 void
990 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
991 {
992 SPStar *star = SP_STAR(item);
994 Geom::Point const s = snap_knot_position(p);
996 if (star->flatsided == false) {
997 Geom::Point d = s - to_2geom(star->center);
999 double arg1 = atan2(d);
1000 double darg1 = arg1 - star->arg[1];
1002 if (state & GDK_MOD1_MASK) {
1003 star->randomized = darg1/(star->arg[0] - star->arg[1]);
1004 } else if (state & GDK_SHIFT_MASK) {
1005 star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1006 } else if (state & GDK_CONTROL_MASK) {
1007 star->r[1] = L2(d);
1008 star->arg[1] = star->arg[0] + M_PI / star->sides;
1009 }
1010 else {
1011 star->r[1] = L2(d);
1012 star->arg[1] = atan2(d);
1013 }
1014 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1015 }
1016 }
1018 Geom::Point
1019 StarKnotHolderEntity1::knot_get()
1020 {
1021 g_assert(item != NULL);
1023 SPStar *star = SP_STAR(item);
1025 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1027 }
1029 Geom::Point
1030 StarKnotHolderEntity2::knot_get()
1031 {
1032 g_assert(item != NULL);
1034 SPStar *star = SP_STAR(item);
1036 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1037 }
1039 static void
1040 sp_star_knot_click(SPItem *item, guint state)
1041 {
1042 SPStar *star = SP_STAR(item);
1044 if (state & GDK_MOD1_MASK) {
1045 star->randomized = 0;
1046 ((SPObject *)star)->updateRepr();
1047 } else if (state & GDK_SHIFT_MASK) {
1048 star->rounded = 0;
1049 ((SPObject *)star)->updateRepr();
1050 } else if (state & GDK_CONTROL_MASK) {
1051 star->arg[1] = star->arg[0] + M_PI / star->sides;
1052 ((SPObject *)star)->updateRepr();
1053 }
1054 }
1056 void
1057 StarKnotHolderEntity1::knot_click(guint state)
1058 {
1059 return sp_star_knot_click(item, state);
1060 }
1062 void
1063 StarKnotHolderEntity2::knot_click(guint state)
1064 {
1065 return sp_star_knot_click(item, state);
1066 }
1068 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1069 KnotHolder(desktop, item, relhandler)
1070 {
1071 SPStar *star = SP_STAR(item);
1073 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1074 entity1->create(desktop, item, this,
1075 _("Adjust the <b>tip radius</b> of the star or polygon; "
1076 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1077 entity.push_back(entity1);
1079 if (star->flatsided == false) {
1080 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1081 entity2->create(desktop, item, this,
1082 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1083 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1084 entity.push_back(entity2);
1085 }
1087 add_pattern_knotholder();
1088 }
1090 /* SPSpiral */
1092 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1093 public:
1094 virtual Geom::Point knot_get();
1095 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1096 virtual void knot_click(guint state);
1097 };
1099 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1100 public:
1101 virtual Geom::Point knot_get();
1102 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1103 };
1106 /*
1107 * set attributes via inner (t=t0) knot point:
1108 * [default] increase/decrease inner point
1109 * [shift] increase/decrease inner and outer arg synchronizely
1110 * [control] constrain inner arg to round per PI/4
1111 */
1112 void
1113 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1114 {
1115 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1116 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1118 SPSpiral *spiral = SP_SPIRAL(item);
1120 gdouble dx = p[Geom::X] - spiral->cx;
1121 gdouble dy = p[Geom::Y] - spiral->cy;
1123 gdouble moved_y = p[Geom::Y] - origin[Geom::Y];
1125 if (state & GDK_MOD1_MASK) {
1126 // adjust divergence by vertical drag, relative to rad
1127 if (spiral->rad > 0) {
1128 double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down
1129 spiral->exp += exp_delta;
1130 if (spiral->exp < 1e-3)
1131 spiral->exp = 1e-3;
1132 }
1133 } else {
1134 // roll/unroll from inside
1135 gdouble arg_t0;
1136 sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1138 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1139 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1140 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1142 /* round inner arg per PI/snaps, if CTRL is pressed */
1143 if ( ( state & GDK_CONTROL_MASK )
1144 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1145 && ( snaps != 0 ) ) {
1146 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1147 spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1148 }
1150 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1151 }
1153 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1154 }
1156 /*
1157 * set attributes via outer (t=1) knot point:
1158 * [default] increase/decrease revolution factor
1159 * [control] constrain inner arg to round per PI/4
1160 */
1161 void
1162 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1163 {
1164 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1165 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1167 SPSpiral *spiral = SP_SPIRAL(item);
1169 gdouble dx = p[Geom::X] - spiral->cx;
1170 gdouble dy = p[Geom::Y] - spiral->cy;
1172 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1173 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1174 if (!(state & GDK_MOD1_MASK)) {
1175 // if alt not pressed, change also rad; otherwise it is locked
1176 spiral->rad = MAX(hypot(dx, dy), 0.001);
1177 }
1178 if ( ( state & GDK_CONTROL_MASK )
1179 && snaps ) {
1180 spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1181 }
1182 } else { // roll/unroll
1183 // arg of the spiral outer end
1184 double arg_1;
1185 sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1187 // its fractional part after the whole turns are subtracted
1188 double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1190 // arg of the mouse point relative to spiral center
1191 double mouse_angle = atan2(dy, dx);
1192 if (mouse_angle < 0)
1193 mouse_angle += 2*M_PI;
1195 // snap if ctrl
1196 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1197 mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1198 }
1200 // by how much we want to rotate the outer point
1201 double diff = mouse_angle - arg_r;
1202 if (diff > M_PI)
1203 diff -= 2*M_PI;
1204 else if (diff < -M_PI)
1205 diff += 2*M_PI;
1207 // calculate the new rad;
1208 // the value of t corresponding to the angle arg_1 + diff:
1209 double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1210 // the rad at that t:
1211 double rad_new = 0;
1212 if (t_temp > spiral->t0)
1213 sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1215 // change the revo (converting diff from radians to the number of turns)
1216 spiral->revo += diff/(2*M_PI);
1217 if (spiral->revo < 1e-3)
1218 spiral->revo = 1e-3;
1220 // if alt not pressed and the values are sane, change the rad
1221 if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1222 // adjust t0 too so that the inner point stays unmoved
1223 double r0;
1224 sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1225 spiral->rad = rad_new;
1226 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1227 }
1228 if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1229 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1230 }
1232 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1233 }
1235 Geom::Point
1236 SpiralKnotHolderEntityInner::knot_get()
1237 {
1238 SPSpiral *spiral = SP_SPIRAL(item);
1240 return sp_spiral_get_xy(spiral, spiral->t0);
1241 }
1243 Geom::Point
1244 SpiralKnotHolderEntityOuter::knot_get()
1245 {
1246 SPSpiral *spiral = SP_SPIRAL(item);
1248 return sp_spiral_get_xy(spiral, 1.0);
1249 }
1251 void
1252 SpiralKnotHolderEntityInner::knot_click(guint state)
1253 {
1254 SPSpiral *spiral = SP_SPIRAL(item);
1256 if (state & GDK_MOD1_MASK) {
1257 spiral->exp = 1;
1258 ((SPObject *)spiral)->updateRepr();
1259 } else if (state & GDK_SHIFT_MASK) {
1260 spiral->t0 = 0;
1261 ((SPObject *)spiral)->updateRepr();
1262 }
1263 }
1265 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1266 KnotHolder(desktop, item, relhandler)
1267 {
1268 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1269 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1270 entity_inner->create(desktop, item, this,
1271 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1272 "with <b>Alt</b> to converge/diverge"));
1273 entity_outer->create(desktop, item, this,
1274 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1275 "with <b>Shift</b> to scale/rotate"));
1276 entity.push_back(entity_inner);
1277 entity.push_back(entity_outer);
1279 add_pattern_knotholder();
1280 }
1282 /* SPOffset */
1284 class OffsetKnotHolderEntity : public KnotHolderEntity {
1285 public:
1286 virtual Geom::Point knot_get();
1287 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1288 };
1290 void
1291 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1292 {
1293 SPOffset *offset = SP_OFFSET(item);
1295 offset->rad = sp_offset_distance_to_original(offset, p);
1296 offset->knot = p;
1297 offset->knotSet = true;
1299 ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1300 }
1303 Geom::Point
1304 OffsetKnotHolderEntity::knot_get()
1305 {
1306 SPOffset *offset = SP_OFFSET(item);
1308 Geom::Point np;
1309 sp_offset_top_point(offset,&np);
1310 return np;
1311 }
1313 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1314 KnotHolder(desktop, item, relhandler)
1315 {
1316 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1317 entity_offset->create(desktop, item, this,
1318 _("Adjust the <b>offset distance</b>"));
1319 entity.push_back(entity_offset);
1321 add_pattern_knotholder();
1322 }
1324 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1325 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1326 // sense logically.
1327 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1328 public:
1329 virtual Geom::Point knot_get();
1330 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1331 };
1333 Geom::Point
1334 FlowtextKnotHolderEntity::knot_get()
1335 {
1336 SPRect *rect = SP_RECT(item);
1338 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1339 }
1341 void
1342 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1343 {
1344 set_internal(p, origin, state);
1345 }
1347 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1348 KnotHolder(desktop, item, relhandler)
1349 {
1350 g_assert(item != NULL);
1352 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1353 entity_flowtext->create(desktop, item, this,
1354 _("Drag to resize the <b>flowed text frame</b>"));
1355 entity.push_back(entity_flowtext);
1356 }
1358 /*
1359 Local Variables:
1360 mode:c++
1361 c-file-style:"stroustrup"
1362 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1363 indent-tabs-mode:nil
1364 fill-column:99
1365 End:
1366 */
1367 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :