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()
129 {
130 SPRect *rect = SP_RECT(item);
132 return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
133 }
135 void
136 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
137 {
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);
158 }
160 void
161 RectKnotHolderEntityRX::knot_click(guint state)
162 {
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 }
174 }
176 Geom::Point
177 RectKnotHolderEntityRY::knot_get()
178 {
179 SPRect *rect = SP_RECT(item);
181 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
182 }
184 void
185 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
186 {
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);
216 }
218 void
219 RectKnotHolderEntityRY::knot_click(guint state)
220 {
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 }
231 }
233 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
235 static void sp_rect_clamp_radii(SPRect *rect)
236 {
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 }
246 }
248 Geom::Point
249 RectKnotHolderEntityWH::knot_get()
250 {
251 SPRect *rect = SP_RECT(item);
253 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
254 }
256 void
257 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
258 {
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);
326 }
328 void
329 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
330 {
331 set_internal(p, origin, state);
332 update_knot();
333 }
335 Geom::Point
336 RectKnotHolderEntityXY::knot_get()
337 {
338 SPRect *rect = SP_RECT(item);
340 return Geom::Point(rect->x.computed, rect->y.computed);
341 }
343 void
344 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
345 {
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);
427 }
429 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
430 KnotHolder(desktop, item, relhandler)
431 {
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();
458 }
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)
473 {
474 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
475 }
477 void
478 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
479 {
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);
496 }
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()
554 {
555 return knot_get_generic(item, 0);
556 }
558 Geom::Point
559 Box3DKnotHolderEntity1::knot_get()
560 {
561 return knot_get_generic(item, 1);
562 }
564 Geom::Point
565 Box3DKnotHolderEntity2::knot_get()
566 {
567 return knot_get_generic(item, 2);
568 }
570 Geom::Point
571 Box3DKnotHolderEntity3::knot_get()
572 {
573 return knot_get_generic(item, 3);
574 }
576 Geom::Point
577 Box3DKnotHolderEntity4::knot_get()
578 {
579 return knot_get_generic(item, 4);
580 }
582 Geom::Point
583 Box3DKnotHolderEntity5::knot_get()
584 {
585 return knot_get_generic(item, 5);
586 }
588 Geom::Point
589 Box3DKnotHolderEntity6::knot_get()
590 {
591 return knot_get_generic(item, 6);
592 }
594 Geom::Point
595 Box3DKnotHolderEntity7::knot_get()
596 {
597 return knot_get_generic(item, 7);
598 }
600 Geom::Point
601 Box3DKnotHolderEntityCenter::knot_get()
602 {
603 return box3d_get_center_screen(SP_BOX3D(item));
604 }
606 void
607 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
608 {
609 knot_set_generic(item, 0, new_pos, state);
610 }
612 void
613 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
614 {
615 knot_set_generic(item, 1, new_pos, state);
616 }
618 void
619 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
620 {
621 knot_set_generic(item, 2, new_pos, state);
622 }
624 void
625 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
626 {
627 knot_set_generic(item, 3, new_pos, state);
628 }
630 void
631 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
632 {
633 knot_set_generic(item, 4, new_pos, state);
634 }
636 void
637 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
638 {
639 knot_set_generic(item, 5, new_pos, state);
640 }
642 void
643 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
644 {
645 knot_set_generic(item, 6, new_pos, state);
646 }
648 void
649 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
650 {
651 knot_set_generic(item, 7, new_pos, state);
652 }
654 void
655 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
656 {
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);
667 }
669 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
670 KnotHolder(desktop, item, relhandler)
671 {
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();
721 }
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)
760 {
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;
768 }
770 void
771 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
772 {
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);
791 }
793 Geom::Point
794 ArcKnotHolderEntityStart::knot_get()
795 {
796 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
797 SPArc *arc = SP_ARC(item);
799 return sp_arc_get_xy(arc, ge->start);
800 }
802 void
803 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
804 {
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);
823 }
825 Geom::Point
826 ArcKnotHolderEntityEnd::knot_get()
827 {
828 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
829 SPArc *arc = SP_ARC(item);
831 return sp_arc_get_xy(arc, ge->end);
832 }
835 void
836 ArcKnotHolderEntityEnd::knot_click(guint state)
837 {
838 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
840 if (state & GDK_SHIFT_MASK) {
841 ge->end = ge->start = 0;
842 ((SPObject *)ge)->updateRepr();
843 }
844 }
847 void
848 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
849 {
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);
861 }
863 Geom::Point
864 ArcKnotHolderEntityRX::knot_get()
865 {
866 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
868 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0));
869 }
871 void
872 ArcKnotHolderEntityRX::knot_click(guint state)
873 {
874 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
876 if (state & GDK_CONTROL_MASK) {
877 ge->ry.computed = ge->rx.computed;
878 ((SPObject *)ge)->updateRepr();
879 }
880 }
882 void
883 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
884 {
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);
896 }
898 Geom::Point
899 ArcKnotHolderEntityRY::knot_get()
900 {
901 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
903 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed));
904 }
906 void
907 ArcKnotHolderEntityRY::knot_click(guint state)
908 {
909 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
911 if (state & GDK_CONTROL_MASK) {
912 ge->rx.computed = ge->ry.computed;
913 ((SPObject *)ge)->updateRepr();
914 }
915 }
917 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
918 KnotHolder(desktop, item, relhandler)
919 {
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();
944 }
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)
964 {
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);
986 }
988 void
989 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
990 {
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 }
1015 }
1017 Geom::Point
1018 StarKnotHolderEntity1::knot_get()
1019 {
1020 g_assert(item != NULL);
1022 SPStar *star = SP_STAR(item);
1024 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1026 }
1028 Geom::Point
1029 StarKnotHolderEntity2::knot_get()
1030 {
1031 g_assert(item != NULL);
1033 SPStar *star = SP_STAR(item);
1035 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1036 }
1038 static void
1039 sp_star_knot_click(SPItem *item, guint state)
1040 {
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 }
1053 }
1055 void
1056 StarKnotHolderEntity1::knot_click(guint state)
1057 {
1058 return sp_star_knot_click(item, state);
1059 }
1061 void
1062 StarKnotHolderEntity2::knot_click(guint state)
1063 {
1064 return sp_star_knot_click(item, state);
1065 }
1067 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1068 KnotHolder(desktop, item, relhandler)
1069 {
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();
1087 }
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)
1113 {
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);
1153 }
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)
1162 {
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);
1232 }
1234 Geom::Point
1235 SpiralKnotHolderEntityInner::knot_get()
1236 {
1237 SPSpiral *spiral = SP_SPIRAL(item);
1239 return sp_spiral_get_xy(spiral, spiral->t0);
1240 }
1242 Geom::Point
1243 SpiralKnotHolderEntityOuter::knot_get()
1244 {
1245 SPSpiral *spiral = SP_SPIRAL(item);
1247 return sp_spiral_get_xy(spiral, 1.0);
1248 }
1250 void
1251 SpiralKnotHolderEntityInner::knot_click(guint state)
1252 {
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 }
1262 }
1264 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1265 KnotHolder(desktop, item, relhandler)
1266 {
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();
1279 }
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*/)
1291 {
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);
1299 }
1302 Geom::Point
1303 OffsetKnotHolderEntity::knot_get()
1304 {
1305 SPOffset *offset = SP_OFFSET(item);
1307 Geom::Point np;
1308 sp_offset_top_point(offset,&np);
1309 return np;
1310 }
1312 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1313 KnotHolder(desktop, item, relhandler)
1314 {
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();
1321 }
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()
1334 {
1335 SPRect *rect = SP_RECT(item);
1337 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1338 }
1340 void
1341 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1342 {
1343 set_internal(p, origin, state);
1344 }
1346 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1347 KnotHolder(desktop, item, relhandler)
1348 {
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);
1355 }
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 :