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::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
146 if (state & GDK_CONTROL_MASK) {
147 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
148 rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
149 rect->rx._set = rect->ry._set = true;
151 } else {
152 rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
153 rect->rx._set = true;
154 }
156 update_knot();
158 ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
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 update_knot();
176 }
178 Geom::Point
179 RectKnotHolderEntityRY::knot_get()
180 {
181 SPRect *rect = SP_RECT(item);
183 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
184 }
186 void
187 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
188 {
189 SPRect *rect = SP_RECT(item);
191 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
192 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
193 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
194 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
196 if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
197 // resulting in a perfect circle (and not an ellipse)
198 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
199 rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
200 rect->ry._set = rect->rx._set = true;
201 } else {
202 if (!rect->rx._set || rect->rx.computed == 0) {
203 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
204 0.0,
205 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
206 } else {
207 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
208 0.0,
209 rect->height.computed / 2.0);
210 }
212 rect->ry._set = true;
213 }
215 update_knot();
217 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
218 }
220 void
221 RectKnotHolderEntityRY::knot_click(guint state)
222 {
223 SPRect *rect = SP_RECT(item);
225 if (state & GDK_SHIFT_MASK) {
226 /* remove rounding */
227 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
228 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
229 } else if (state & GDK_CONTROL_MASK) {
230 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
231 SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
232 }
233 }
235 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
237 static void sp_rect_clamp_radii(SPRect *rect)
238 {
239 // clamp rounding radii so that they do not exceed width/height
240 if (2 * rect->rx.computed > rect->width.computed) {
241 rect->rx.computed = 0.5 * rect->width.computed;
242 rect->rx._set = true;
243 }
244 if (2 * rect->ry.computed > rect->height.computed) {
245 rect->ry.computed = 0.5 * rect->height.computed;
246 rect->ry._set = true;
247 }
248 }
250 Geom::Point
251 RectKnotHolderEntityWH::knot_get()
252 {
253 SPRect *rect = SP_RECT(item);
255 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
256 }
258 void
259 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
260 {
261 SPRect *rect = SP_RECT(item);
263 Geom::Point s = p;
265 if (state & GDK_CONTROL_MASK) {
266 // original width/height when drag started
267 gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
268 gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
270 //original ratio
271 gdouble ratio = (w_orig / h_orig);
273 // mouse displacement since drag started
274 gdouble minx = p[Geom::X] - origin[Geom::X];
275 gdouble miny = p[Geom::Y] - origin[Geom::Y];
277 Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
279 if (fabs(minx) > fabs(miny)) {
280 // snap to horizontal or diagonal
281 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
282 // closer to the diagonal and in same-sign quarters, change both using ratio
283 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
284 minx = s[Geom::X] - origin[Geom::X];
285 miny = s[Geom::Y] - origin[Geom::Y];
286 rect->height.computed = MAX(h_orig + minx / ratio, 0);
287 } else {
288 // closer to the horizontal, change only width, height is h_orig
289 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)));
290 minx = s[Geom::X] - origin[Geom::X];
291 miny = s[Geom::Y] - origin[Geom::Y];
292 rect->height.computed = MAX(h_orig, 0);
293 }
294 rect->width.computed = MAX(w_orig + minx, 0);
296 } else {
297 // snap to vertical or diagonal
298 if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
299 // closer to the diagonal and in same-sign quarters, change both using ratio
300 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
301 minx = s[Geom::X] - origin[Geom::X];
302 miny = s[Geom::Y] - origin[Geom::Y];
303 rect->width.computed = MAX(w_orig + miny * ratio, 0);
304 } else {
305 // closer to the vertical, change only height, width is w_orig
306 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)));
307 minx = s[Geom::X] - origin[Geom::X];
308 miny = s[Geom::Y] - origin[Geom::Y];
309 rect->width.computed = MAX(w_orig, 0);
310 }
311 rect->height.computed = MAX(h_orig + miny, 0);
313 }
315 rect->width._set = rect->height._set = true;
317 } else {
318 // move freely
319 s = snap_knot_position(p);
320 rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
321 rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
322 rect->width._set = rect->height._set = true;
323 }
325 sp_rect_clamp_radii(rect);
327 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328 }
330 void
331 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
332 {
333 set_internal(p, origin, state);
334 update_knot();
335 }
337 Geom::Point
338 RectKnotHolderEntityXY::knot_get()
339 {
340 SPRect *rect = SP_RECT(item);
342 return Geom::Point(rect->x.computed, rect->y.computed);
343 }
345 void
346 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
347 {
348 SPRect *rect = SP_RECT(item);
350 // opposite corner (unmoved)
351 gdouble opposite_x = (rect->x.computed + rect->width.computed);
352 gdouble opposite_y = (rect->y.computed + rect->height.computed);
354 // original width/height when drag started
355 gdouble w_orig = opposite_x - origin[Geom::X];
356 gdouble h_orig = opposite_y - origin[Geom::Y];
358 Geom::Point s = p;
359 Geom::Point p_handle(rect->x.computed, rect->y.computed);
361 // mouse displacement since drag started
362 gdouble minx = p[Geom::X] - origin[Geom::X];
363 gdouble miny = p[Geom::Y] - origin[Geom::Y];
365 if (state & GDK_CONTROL_MASK) {
366 //original ratio
367 gdouble ratio = (w_orig / h_orig);
369 if (fabs(minx) > fabs(miny)) {
370 // snap to horizontal or diagonal
371 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
372 // closer to the diagonal and in same-sign quarters, change both using ratio
373 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
374 minx = s[Geom::X] - origin[Geom::X];
375 miny = s[Geom::Y] - origin[Geom::Y];
376 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
377 rect->height.computed = MAX(h_orig - minx / ratio, 0);
378 } else {
379 // closer to the horizontal, change only width, height is h_orig
380 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-1, 0)));
381 minx = s[Geom::X] - origin[Geom::X];
382 miny = s[Geom::Y] - origin[Geom::Y];
383 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
384 rect->height.computed = MAX(h_orig, 0);
385 }
386 rect->x.computed = MIN(s[Geom::X], opposite_x);
387 rect->width.computed = MAX(w_orig - minx, 0);
388 } else {
389 // snap to vertical or diagonal
390 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
391 // closer to the diagonal and in same-sign quarters, change both using ratio
392 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(-ratio, -1)));
393 minx = s[Geom::X] - origin[Geom::X];
394 miny = s[Geom::Y] - origin[Geom::Y];
395 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
396 rect->width.computed = MAX(w_orig - miny * ratio, 0);
397 } else {
398 // closer to the vertical, change only height, width is w_orig
399 s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(p_handle, Geom::Point(0, -1)));
400 minx = s[Geom::X] - origin[Geom::X];
401 miny = s[Geom::Y] - origin[Geom::Y];
402 rect->x.computed = MIN(origin[Geom::X], opposite_x);
403 rect->width.computed = MAX(w_orig, 0);
404 }
405 rect->y.computed = MIN(s[Geom::Y], opposite_y);
406 rect->height.computed = MAX(h_orig - miny, 0);
407 }
409 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
411 } else {
412 // move freely
413 s = snap_knot_position(p);
414 minx = s[Geom::X] - origin[Geom::X];
415 miny = s[Geom::Y] - origin[Geom::Y];
417 rect->x.computed = MIN(s[Geom::X], opposite_x);
418 rect->width.computed = MAX(w_orig - minx, 0);
419 rect->y.computed = MIN(s[Geom::Y], opposite_y);
420 rect->height.computed = MAX(h_orig - miny, 0);
421 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
422 }
424 sp_rect_clamp_radii(rect);
426 update_knot();
428 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
429 }
431 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
432 KnotHolder(desktop, item, relhandler)
433 {
434 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
435 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
436 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
437 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
438 entity_rx->create(desktop, item, this,
439 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
440 "to make the vertical radius the same"),
441 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
442 entity_ry->create(desktop, item, this,
443 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
444 "to make the horizontal radius the same"),
445 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
446 entity_wh->create(desktop, item, this,
447 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
448 "to lock ratio or stretch in one dimension only"),
449 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
450 entity_xy->create(desktop, item, this,
451 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
452 "to lock ratio or stretch in one dimension only"),
453 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
454 entity.push_back(entity_rx);
455 entity.push_back(entity_ry);
456 entity.push_back(entity_wh);
457 entity.push_back(entity_xy);
459 add_pattern_knotholder();
460 }
462 /* Box3D (= the new 3D box structure) */
464 class Box3DKnotHolderEntity : public KnotHolderEntity {
465 public:
466 virtual Geom::Point knot_get() = 0;
467 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
469 Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
470 void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
471 };
473 Geom::Point
474 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
475 {
476 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
477 }
479 void
480 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
481 {
482 Geom::Point const s = snap_knot_position(new_pos);
484 g_assert(item != NULL);
485 SPBox3D *box = SP_BOX3D(item);
486 Geom::Matrix const i2d (sp_item_i2d_affine (item));
488 Box3D::Axis movement;
489 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
490 movement = Box3D::XY;
491 } else {
492 movement = Box3D::Z;
493 }
495 box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
496 box3d_set_z_orders(box);
497 box3d_position_set(box);
498 }
500 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
501 public:
502 virtual Geom::Point knot_get();
503 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
504 };
506 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
507 public:
508 virtual Geom::Point knot_get();
509 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
510 };
512 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
513 public:
514 virtual Geom::Point knot_get();
515 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
516 };
518 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
519 public:
520 virtual Geom::Point knot_get();
521 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
522 };
524 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
525 public:
526 virtual Geom::Point knot_get();
527 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
528 };
530 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
531 public:
532 virtual Geom::Point knot_get();
533 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
534 };
536 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
537 public:
538 virtual Geom::Point knot_get();
539 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
540 };
542 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
543 public:
544 virtual Geom::Point knot_get();
545 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
546 };
548 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
549 public:
550 virtual Geom::Point knot_get();
551 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
552 };
554 Geom::Point
555 Box3DKnotHolderEntity0::knot_get()
556 {
557 return knot_get_generic(item, 0);
558 }
560 Geom::Point
561 Box3DKnotHolderEntity1::knot_get()
562 {
563 return knot_get_generic(item, 1);
564 }
566 Geom::Point
567 Box3DKnotHolderEntity2::knot_get()
568 {
569 return knot_get_generic(item, 2);
570 }
572 Geom::Point
573 Box3DKnotHolderEntity3::knot_get()
574 {
575 return knot_get_generic(item, 3);
576 }
578 Geom::Point
579 Box3DKnotHolderEntity4::knot_get()
580 {
581 return knot_get_generic(item, 4);
582 }
584 Geom::Point
585 Box3DKnotHolderEntity5::knot_get()
586 {
587 return knot_get_generic(item, 5);
588 }
590 Geom::Point
591 Box3DKnotHolderEntity6::knot_get()
592 {
593 return knot_get_generic(item, 6);
594 }
596 Geom::Point
597 Box3DKnotHolderEntity7::knot_get()
598 {
599 return knot_get_generic(item, 7);
600 }
602 Geom::Point
603 Box3DKnotHolderEntityCenter::knot_get()
604 {
605 return box3d_get_center_screen(SP_BOX3D(item));
606 }
608 void
609 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
610 {
611 knot_set_generic(item, 0, new_pos, state);
612 }
614 void
615 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
616 {
617 knot_set_generic(item, 1, new_pos, state);
618 }
620 void
621 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
622 {
623 knot_set_generic(item, 2, new_pos, state);
624 }
626 void
627 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
628 {
629 knot_set_generic(item, 3, new_pos, state);
630 }
632 void
633 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
634 {
635 knot_set_generic(item, 4, new_pos, state);
636 }
638 void
639 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
640 {
641 knot_set_generic(item, 5, new_pos, state);
642 }
644 void
645 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
646 {
647 knot_set_generic(item, 6, new_pos, state);
648 }
650 void
651 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
652 {
653 knot_set_generic(item, 7, new_pos, state);
654 }
656 void
657 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
658 {
659 Geom::Point const s = snap_knot_position(new_pos);
661 SPBox3D *box = SP_BOX3D(item);
662 Geom::Matrix const i2d (sp_item_i2d_affine (item));
664 box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
665 state & GDK_CONTROL_MASK);
667 box3d_set_z_orders(box);
668 box3d_position_set(box);
669 }
671 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
672 KnotHolder(desktop, item, relhandler)
673 {
674 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
675 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
676 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
677 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
678 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
679 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
680 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
681 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
682 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
684 entity_corner0->create(desktop, item, this,
685 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
686 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
687 entity_corner1->create(desktop, item, this,
688 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
689 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
690 entity_corner2->create(desktop, item, this,
691 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
692 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
693 entity_corner3->create(desktop, item, this,
694 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
695 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
696 entity_corner4->create(desktop, item, this,
697 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
698 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
699 entity_corner5->create(desktop, item, this,
700 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
701 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
702 entity_corner6->create(desktop, item, this,
703 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
704 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
705 entity_corner7->create(desktop, item, this,
706 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
707 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
708 entity_center->create(desktop, item, this,
709 _("Move the box in perspective"),
710 SP_KNOT_SHAPE_CROSS);
712 entity.push_back(entity_corner0);
713 entity.push_back(entity_corner1);
714 entity.push_back(entity_corner2);
715 entity.push_back(entity_corner3);
716 entity.push_back(entity_corner4);
717 entity.push_back(entity_corner5);
718 entity.push_back(entity_corner6);
719 entity.push_back(entity_corner7);
720 entity.push_back(entity_center);
722 add_pattern_knotholder();
723 }
725 /* SPArc */
727 class ArcKnotHolderEntityStart : public KnotHolderEntity {
728 public:
729 virtual Geom::Point knot_get();
730 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
731 };
733 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
734 public:
735 virtual Geom::Point knot_get();
736 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
737 virtual void knot_click(guint state);
738 };
740 class ArcKnotHolderEntityRX : public KnotHolderEntity {
741 public:
742 virtual Geom::Point knot_get();
743 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
744 virtual void knot_click(guint state);
745 };
747 class ArcKnotHolderEntityRY : public KnotHolderEntity {
748 public:
749 virtual Geom::Point knot_get();
750 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
751 virtual void knot_click(guint state);
752 };
754 /*
755 * return values:
756 * 1 : inside
757 * 0 : on the curves
758 * -1 : outside
759 */
760 static gint
761 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
762 {
763 gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
764 gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
766 gdouble s = dx * dx + dy * dy;
767 if (s < 1.0) return 1;
768 if (s > 1.0) return -1;
769 return 0;
770 }
772 void
773 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
774 {
775 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
776 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
778 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
779 SPArc *arc = SP_ARC(item);
781 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
783 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
784 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
785 ge->start = atan2(delta * sc.inverse());
786 if ( ( state & GDK_CONTROL_MASK )
787 && snaps )
788 {
789 ge->start = sp_round(ge->start, M_PI/snaps);
790 }
791 sp_genericellipse_normalize(ge);
792 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
793 }
795 Geom::Point
796 ArcKnotHolderEntityStart::knot_get()
797 {
798 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
799 SPArc *arc = SP_ARC(item);
801 return sp_arc_get_xy(arc, ge->start);
802 }
804 void
805 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
806 {
807 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
808 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
810 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
811 SPArc *arc = SP_ARC(item);
813 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
815 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
816 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
817 ge->end = atan2(delta * sc.inverse());
818 if ( ( state & GDK_CONTROL_MASK )
819 && snaps )
820 {
821 ge->end = sp_round(ge->end, M_PI/snaps);
822 }
823 sp_genericellipse_normalize(ge);
824 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
825 }
827 Geom::Point
828 ArcKnotHolderEntityEnd::knot_get()
829 {
830 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
831 SPArc *arc = SP_ARC(item);
833 return sp_arc_get_xy(arc, ge->end);
834 }
837 void
838 ArcKnotHolderEntityEnd::knot_click(guint state)
839 {
840 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
842 if (state & GDK_SHIFT_MASK) {
843 ge->end = ge->start = 0;
844 ((SPObject *)ge)->updateRepr();
845 }
846 }
849 void
850 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
851 {
852 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
854 Geom::Point const s = snap_knot_position(p);
856 ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
858 if ( state & GDK_CONTROL_MASK ) {
859 ge->ry.computed = ge->rx.computed;
860 }
862 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
863 }
865 Geom::Point
866 ArcKnotHolderEntityRX::knot_get()
867 {
868 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
870 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0));
871 }
873 void
874 ArcKnotHolderEntityRX::knot_click(guint state)
875 {
876 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
878 if (state & GDK_CONTROL_MASK) {
879 ge->ry.computed = ge->rx.computed;
880 ((SPObject *)ge)->updateRepr();
881 }
882 }
884 void
885 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
886 {
887 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
889 Geom::Point const s = snap_knot_position(p);
891 ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
893 if ( state & GDK_CONTROL_MASK ) {
894 ge->rx.computed = ge->ry.computed;
895 }
897 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
898 }
900 Geom::Point
901 ArcKnotHolderEntityRY::knot_get()
902 {
903 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
905 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed));
906 }
908 void
909 ArcKnotHolderEntityRY::knot_click(guint state)
910 {
911 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
913 if (state & GDK_CONTROL_MASK) {
914 ge->rx.computed = ge->ry.computed;
915 ((SPObject *)ge)->updateRepr();
916 }
917 }
919 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
920 KnotHolder(desktop, item, relhandler)
921 {
922 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
923 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
924 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
925 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
926 entity_rx->create(desktop, item, this,
927 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
928 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
929 entity_ry->create(desktop, item, this,
930 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
931 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
932 entity_start->create(desktop, item, this,
933 _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
934 "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
935 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
936 entity_end->create(desktop, item, this,
937 _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
938 "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
939 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
940 entity.push_back(entity_rx);
941 entity.push_back(entity_ry);
942 entity.push_back(entity_start);
943 entity.push_back(entity_end);
945 add_pattern_knotholder();
946 }
948 /* SPStar */
950 class StarKnotHolderEntity1 : public KnotHolderEntity {
951 public:
952 virtual Geom::Point knot_get();
953 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
954 virtual void knot_click(guint state);
955 };
957 class StarKnotHolderEntity2 : public KnotHolderEntity {
958 public:
959 virtual Geom::Point knot_get();
960 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
961 virtual void knot_click(guint state);
962 };
964 void
965 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
966 {
967 SPStar *star = SP_STAR(item);
969 Geom::Point const s = snap_knot_position(p);
971 Geom::Point d = s - to_2geom(star->center);
973 double arg1 = atan2(d);
974 double darg1 = arg1 - star->arg[0];
976 if (state & GDK_MOD1_MASK) {
977 star->randomized = darg1/(star->arg[0] - star->arg[1]);
978 } else if (state & GDK_SHIFT_MASK) {
979 star->rounded = darg1/(star->arg[0] - star->arg[1]);
980 } else if (state & GDK_CONTROL_MASK) {
981 star->r[0] = L2(d);
982 } else {
983 star->r[0] = L2(d);
984 star->arg[0] = arg1;
985 star->arg[1] += darg1;
986 }
987 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
988 }
990 void
991 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
992 {
993 SPStar *star = SP_STAR(item);
995 Geom::Point const s = snap_knot_position(p);
997 if (star->flatsided == false) {
998 Geom::Point d = s - to_2geom(star->center);
1000 double arg1 = atan2(d);
1001 double darg1 = arg1 - star->arg[1];
1003 if (state & GDK_MOD1_MASK) {
1004 star->randomized = darg1/(star->arg[0] - star->arg[1]);
1005 } else if (state & GDK_SHIFT_MASK) {
1006 star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1007 } else if (state & GDK_CONTROL_MASK) {
1008 star->r[1] = L2(d);
1009 star->arg[1] = star->arg[0] + M_PI / star->sides;
1010 }
1011 else {
1012 star->r[1] = L2(d);
1013 star->arg[1] = atan2(d);
1014 }
1015 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1016 }
1017 }
1019 Geom::Point
1020 StarKnotHolderEntity1::knot_get()
1021 {
1022 g_assert(item != NULL);
1024 SPStar *star = SP_STAR(item);
1026 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1028 }
1030 Geom::Point
1031 StarKnotHolderEntity2::knot_get()
1032 {
1033 g_assert(item != NULL);
1035 SPStar *star = SP_STAR(item);
1037 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1038 }
1040 static void
1041 sp_star_knot_click(SPItem *item, guint state)
1042 {
1043 SPStar *star = SP_STAR(item);
1045 if (state & GDK_MOD1_MASK) {
1046 star->randomized = 0;
1047 ((SPObject *)star)->updateRepr();
1048 } else if (state & GDK_SHIFT_MASK) {
1049 star->rounded = 0;
1050 ((SPObject *)star)->updateRepr();
1051 } else if (state & GDK_CONTROL_MASK) {
1052 star->arg[1] = star->arg[0] + M_PI / star->sides;
1053 ((SPObject *)star)->updateRepr();
1054 }
1055 }
1057 void
1058 StarKnotHolderEntity1::knot_click(guint state)
1059 {
1060 return sp_star_knot_click(item, state);
1061 }
1063 void
1064 StarKnotHolderEntity2::knot_click(guint state)
1065 {
1066 return sp_star_knot_click(item, state);
1067 }
1069 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1070 KnotHolder(desktop, item, relhandler)
1071 {
1072 SPStar *star = SP_STAR(item);
1074 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1075 entity1->create(desktop, item, this,
1076 _("Adjust the <b>tip radius</b> of the star or polygon; "
1077 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1078 entity.push_back(entity1);
1080 if (star->flatsided == false) {
1081 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1082 entity2->create(desktop, item, this,
1083 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1084 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1085 entity.push_back(entity2);
1086 }
1088 add_pattern_knotholder();
1089 }
1091 /* SPSpiral */
1093 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1094 public:
1095 virtual Geom::Point knot_get();
1096 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1097 virtual void knot_click(guint state);
1098 };
1100 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1101 public:
1102 virtual Geom::Point knot_get();
1103 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1104 };
1107 /*
1108 * set attributes via inner (t=t0) knot point:
1109 * [default] increase/decrease inner point
1110 * [shift] increase/decrease inner and outer arg synchronizely
1111 * [control] constrain inner arg to round per PI/4
1112 */
1113 void
1114 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1115 {
1116 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1117 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1119 SPSpiral *spiral = SP_SPIRAL(item);
1121 gdouble dx = p[Geom::X] - spiral->cx;
1122 gdouble dy = p[Geom::Y] - spiral->cy;
1124 if (state & GDK_MOD1_MASK) {
1125 // adjust divergence by vertical drag, relative to rad
1126 double new_exp = (spiral->rad + dy)/(spiral->rad);
1127 spiral->exp = new_exp > 0? new_exp : 0;
1128 } else {
1129 // roll/unroll from inside
1130 gdouble arg_t0;
1131 sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1133 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1134 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1135 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1137 /* round inner arg per PI/snaps, if CTRL is pressed */
1138 if ( ( state & GDK_CONTROL_MASK )
1139 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1140 && ( snaps != 0 ) ) {
1141 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1142 spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1143 }
1145 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1146 }
1148 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1149 }
1151 /*
1152 * set attributes via outer (t=1) knot point:
1153 * [default] increase/decrease revolution factor
1154 * [control] constrain inner arg to round per PI/4
1155 */
1156 void
1157 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1158 {
1159 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1160 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1162 SPSpiral *spiral = SP_SPIRAL(item);
1164 gdouble dx = p[Geom::X] - spiral->cx;
1165 gdouble dy = p[Geom::Y] - spiral->cy;
1167 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1168 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1169 if (!(state & GDK_MOD1_MASK)) {
1170 // if alt not pressed, change also rad; otherwise it is locked
1171 spiral->rad = MAX(hypot(dx, dy), 0.001);
1172 }
1173 if ( ( state & GDK_CONTROL_MASK )
1174 && snaps ) {
1175 spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1176 }
1177 } else { // roll/unroll
1178 // arg of the spiral outer end
1179 double arg_1;
1180 sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1182 // its fractional part after the whole turns are subtracted
1183 double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1185 // arg of the mouse point relative to spiral center
1186 double mouse_angle = atan2(dy, dx);
1187 if (mouse_angle < 0)
1188 mouse_angle += 2*M_PI;
1190 // snap if ctrl
1191 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1192 mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1193 }
1195 // by how much we want to rotate the outer point
1196 double diff = mouse_angle - arg_r;
1197 if (diff > M_PI)
1198 diff -= 2*M_PI;
1199 else if (diff < -M_PI)
1200 diff += 2*M_PI;
1202 // calculate the new rad;
1203 // the value of t corresponding to the angle arg_1 + diff:
1204 double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1205 // the rad at that t:
1206 double rad_new = 0;
1207 if (t_temp > spiral->t0)
1208 sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1210 // change the revo (converting diff from radians to the number of turns)
1211 spiral->revo += diff/(2*M_PI);
1212 if (spiral->revo < 1e-3)
1213 spiral->revo = 1e-3;
1215 // if alt not pressed and the values are sane, change the rad
1216 if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1217 // adjust t0 too so that the inner point stays unmoved
1218 double r0;
1219 sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1220 spiral->rad = rad_new;
1221 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1222 }
1223 if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1224 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1225 }
1227 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1228 }
1230 Geom::Point
1231 SpiralKnotHolderEntityInner::knot_get()
1232 {
1233 SPSpiral *spiral = SP_SPIRAL(item);
1235 return sp_spiral_get_xy(spiral, spiral->t0);
1236 }
1238 Geom::Point
1239 SpiralKnotHolderEntityOuter::knot_get()
1240 {
1241 SPSpiral *spiral = SP_SPIRAL(item);
1243 return sp_spiral_get_xy(spiral, 1.0);
1244 }
1246 void
1247 SpiralKnotHolderEntityInner::knot_click(guint state)
1248 {
1249 SPSpiral *spiral = SP_SPIRAL(item);
1251 if (state & GDK_MOD1_MASK) {
1252 spiral->exp = 1;
1253 ((SPObject *)spiral)->updateRepr();
1254 } else if (state & GDK_SHIFT_MASK) {
1255 spiral->t0 = 0;
1256 ((SPObject *)spiral)->updateRepr();
1257 }
1258 }
1260 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1261 KnotHolder(desktop, item, relhandler)
1262 {
1263 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1264 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1265 entity_inner->create(desktop, item, this,
1266 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1267 "with <b>Alt</b> to converge/diverge"));
1268 entity_outer->create(desktop, item, this,
1269 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1270 "with <b>Shift</b> to scale/rotate"));
1271 entity.push_back(entity_inner);
1272 entity.push_back(entity_outer);
1274 add_pattern_knotholder();
1275 }
1277 /* SPOffset */
1279 class OffsetKnotHolderEntity : public KnotHolderEntity {
1280 public:
1281 virtual Geom::Point knot_get();
1282 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1283 };
1285 void
1286 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1287 {
1288 SPOffset *offset = SP_OFFSET(item);
1290 offset->rad = sp_offset_distance_to_original(offset, p);
1291 offset->knot = p;
1292 offset->knotSet = true;
1294 ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1295 }
1298 Geom::Point
1299 OffsetKnotHolderEntity::knot_get()
1300 {
1301 SPOffset *offset = SP_OFFSET(item);
1303 Geom::Point np;
1304 sp_offset_top_point(offset,&np);
1305 return np;
1306 }
1308 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1309 KnotHolder(desktop, item, relhandler)
1310 {
1311 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1312 entity_offset->create(desktop, item, this,
1313 _("Adjust the <b>offset distance</b>"));
1314 entity.push_back(entity_offset);
1316 add_pattern_knotholder();
1317 }
1319 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1320 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1321 // sense logically.
1322 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1323 public:
1324 virtual Geom::Point knot_get();
1325 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1326 };
1328 Geom::Point
1329 FlowtextKnotHolderEntity::knot_get()
1330 {
1331 SPRect *rect = SP_RECT(item);
1333 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1334 }
1336 void
1337 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1338 {
1339 set_internal(p, origin, state);
1340 }
1342 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1343 KnotHolder(desktop, item, relhandler)
1344 {
1345 g_assert(item != NULL);
1347 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1348 entity_flowtext->create(desktop, item, this,
1349 _("Drag to resize the <b>flowed text frame</b>"));
1350 entity.push_back(entity_flowtext);
1351 }
1353 /*
1354 Local Variables:
1355 mode:c++
1356 c-file-style:"stroustrup"
1357 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1358 indent-tabs-mode:nil
1359 fill-column:99
1360 End:
1361 */
1362 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :