1 #define __SP_OBJECT_EDIT_C__
3 /*
4 * Node editing extension to objects
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Mitsuru Oka
9 * Maximilian Albert <maximilian.albert@gmail.com>
10 *
11 * Licensed under GNU GPL
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
20 #include "sp-item.h"
21 #include "sp-rect.h"
22 #include "box3d.h"
23 #include "sp-ellipse.h"
24 #include "sp-star.h"
25 #include "sp-spiral.h"
26 #include "sp-offset.h"
27 #include "sp-flowtext.h"
28 #include "prefs-utils.h"
29 #include "inkscape.h"
30 #include "snap.h"
31 #include "desktop-affine.h"
32 #include "style.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "sp-namedview.h"
36 #include "live_effects/effect.h"
38 #include "sp-pattern.h"
39 #include "sp-path.h"
41 #include <glibmm/i18n.h>
43 #include "object-edit.h"
45 #include <libnr/nr-scale-ops.h>
46 #include <libnr/nr-matrix-div.h>
48 #include "xml/repr.h"
50 #include "2geom/isnan.h"
52 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
54 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
55 {
56 KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
58 Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
59 effect->addPointParamHandles(knot_holder, desktop, item);
60 effect->addHandles(knot_holder, desktop, item);
62 return knot_holder;
63 }
65 KnotHolder *
66 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
67 {
68 KnotHolder *knotholder = NULL;
70 if (SP_IS_LPE_ITEM(item) &&
71 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
72 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
73 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
74 knotholder = sp_lpe_knot_holder(item, desktop);
75 } else if (SP_IS_RECT(item)) {
76 knotholder = new RectKnotHolder(desktop, item, NULL);
77 } else if (SP_IS_BOX3D(item)) {
78 knotholder = new Box3DKnotHolder(desktop, item, NULL);
79 } else if (SP_IS_ARC(item)) {
80 knotholder = new ArcKnotHolder(desktop, item, NULL);
81 } else if (SP_IS_STAR(item)) {
82 knotholder = new StarKnotHolder(desktop, item, NULL);
83 } else if (SP_IS_SPIRAL(item)) {
84 knotholder = new SpiralKnotHolder(desktop, item, NULL);
85 } else if (SP_IS_OFFSET(item)) {
86 knotholder = new OffsetKnotHolder(desktop, item, NULL);
87 } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
88 knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
89 } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
90 && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
91 knotholder = new KnotHolder(desktop, item, NULL);
92 knotholder->add_pattern_knotholder();
93 }
95 return knotholder;
96 }
98 /* SPRect */
100 /* handle for horizontal rounding radius */
101 class RectKnotHolderEntityRX : public KnotHolderEntity {
102 public:
103 virtual NR::Point knot_get();
104 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
105 virtual void knot_click(guint state);
106 };
108 /* handle for vertical rounding radius */
109 class RectKnotHolderEntityRY : public KnotHolderEntity {
110 public:
111 virtual NR::Point knot_get();
112 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
113 virtual void knot_click(guint state);
114 };
116 /* handle for width/height adjustment */
117 class RectKnotHolderEntityWH : public KnotHolderEntity {
118 public:
119 virtual NR::Point knot_get();
120 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
121 };
123 /* handle for x/y adjustment */
124 class RectKnotHolderEntityXY : public KnotHolderEntity {
125 public:
126 virtual NR::Point knot_get();
127 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
128 };
130 static NR::Point snap_knot_position(SPItem *item, NR::Point const &p)
131 {
132 SPDesktop const *desktop = inkscape_active_desktop();
133 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
134 NR::Point s = p * i2d;
135 SnapManager &m = desktop->namedview->snap_manager;
136 m.setup(desktop, item);
137 m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, s);
138 return s * i2d.inverse();
139 }
141 NR::Point
142 RectKnotHolderEntityRX::knot_get()
143 {
144 SPRect *rect = SP_RECT(item);
146 return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
147 }
149 void
150 RectKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
151 {
152 SPRect *rect = SP_RECT(item);
154 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
155 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
156 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
158 if (state & GDK_CONTROL_MASK) {
159 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
160 rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
161 rect->rx._set = rect->ry._set = true;
163 } else {
164 rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
165 rect->rx._set = true;
166 }
168 update_knot();
170 ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
171 }
173 void
174 RectKnotHolderEntityRX::knot_click(guint state)
175 {
176 SPRect *rect = SP_RECT(item);
178 if (state & GDK_SHIFT_MASK) {
179 /* remove rounding from rectangle */
180 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
181 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
182 } else if (state & GDK_CONTROL_MASK) {
183 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
184 SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
185 }
187 update_knot();
188 }
190 NR::Point
191 RectKnotHolderEntityRY::knot_get()
192 {
193 SPRect *rect = SP_RECT(item);
195 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
196 }
198 void
199 RectKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
200 {
201 SPRect *rect = SP_RECT(item);
203 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
204 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
205 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
207 if (state & GDK_CONTROL_MASK) {
208 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
209 rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
210 rect->ry._set = rect->rx._set = true;
211 } else {
212 if (!rect->rx._set || rect->rx.computed == 0) {
213 rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
214 0.0,
215 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
216 } else {
217 rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
218 0.0,
219 rect->height.computed / 2.0);
220 }
222 rect->ry._set = true;
223 }
225 update_knot();
227 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
228 }
230 void
231 RectKnotHolderEntityRY::knot_click(guint state)
232 {
233 SPRect *rect = SP_RECT(item);
235 if (state & GDK_SHIFT_MASK) {
236 /* remove rounding */
237 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
238 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
239 } else if (state & GDK_CONTROL_MASK) {
240 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
241 SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
242 }
243 }
245 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
247 static void sp_rect_clamp_radii(SPRect *rect)
248 {
249 // clamp rounding radii so that they do not exceed width/height
250 if (2 * rect->rx.computed > rect->width.computed) {
251 rect->rx.computed = 0.5 * rect->width.computed;
252 rect->rx._set = true;
253 }
254 if (2 * rect->ry.computed > rect->height.computed) {
255 rect->ry.computed = 0.5 * rect->height.computed;
256 rect->ry._set = true;
257 }
258 }
260 NR::Point
261 RectKnotHolderEntityWH::knot_get()
262 {
263 SPRect *rect = SP_RECT(item);
265 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
266 }
268 static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
269 {
270 NR::Point const s = snap_knot_position(rect, p);
272 if (state & GDK_CONTROL_MASK) {
273 // original width/height when drag started
274 gdouble const w_orig = (origin[NR::X] - rect->x.computed);
275 gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
277 //original ratio
278 gdouble const ratio = (w_orig / h_orig);
280 // mouse displacement since drag started
281 gdouble const minx = s[NR::X] - origin[NR::X];
282 gdouble const miny = s[NR::Y] - origin[NR::Y];
284 if (fabs(minx) > fabs(miny)) {
286 // snap to horizontal or diagonal
287 rect->width.computed = MAX(w_orig + minx, 0);
288 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
289 // closer to the diagonal and in same-sign quarters, change both using ratio
290 rect->height.computed = MAX(h_orig + minx / ratio, 0);
291 } else {
292 // closer to the horizontal, change only width, height is h_orig
293 rect->height.computed = MAX(h_orig, 0);
294 }
296 } else {
297 // snap to vertical or diagonal
298 rect->height.computed = MAX(h_orig + miny, 0);
299 if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
300 // closer to the diagonal and in same-sign quarters, change both using ratio
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 rect->width.computed = MAX(w_orig, 0);
305 }
306 }
308 rect->width._set = rect->height._set = true;
310 } else {
311 // move freely
312 rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
313 rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
314 rect->width._set = rect->height._set = true;
315 }
317 sp_rect_clamp_radii(rect);
319 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
320 }
322 void
323 RectKnotHolderEntityWH::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
324 {
325 SPRect *rect = SP_RECT(item);
327 sp_rect_wh_set_internal(rect, p, origin, state);
329 update_knot();
330 }
332 NR::Point
333 RectKnotHolderEntityXY::knot_get()
334 {
335 SPRect *rect = SP_RECT(item);
337 return NR::Point(rect->x.computed, rect->y.computed);
338 }
340 void
341 RectKnotHolderEntityXY::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
342 {
343 SPRect *rect = SP_RECT(item);
345 // opposite corner (unmoved)
346 gdouble opposite_x = (rect->x.computed + rect->width.computed);
347 gdouble opposite_y = (rect->y.computed + rect->height.computed);
349 // original width/height when drag started
350 gdouble w_orig = opposite_x - origin[NR::X];
351 gdouble h_orig = opposite_y - origin[NR::Y];
353 NR::Point const s = snap_knot_position(rect, p);
355 // mouse displacement since drag started
356 gdouble minx = s[NR::X] - origin[NR::X];
357 gdouble miny = s[NR::Y] - origin[NR::Y];
359 if (state & GDK_CONTROL_MASK) {
360 //original ratio
361 gdouble ratio = (w_orig / h_orig);
363 if (fabs(minx) > fabs(miny)) {
365 // snap to horizontal or diagonal
366 rect->x.computed = MIN(s[NR::X], opposite_x);
367 rect->width.computed = MAX(w_orig - minx, 0);
368 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
369 // closer to the diagonal and in same-sign quarters, change both using ratio
370 rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
371 rect->height.computed = MAX(h_orig - minx / ratio, 0);
372 } else {
373 // closer to the horizontal, change only width, height is h_orig
374 rect->y.computed = MIN(origin[NR::Y], opposite_y);
375 rect->height.computed = MAX(h_orig, 0);
376 }
378 } else {
380 // snap to vertical or diagonal
381 rect->y.computed = MIN(s[NR::Y], opposite_y);
382 rect->height.computed = MAX(h_orig - miny, 0);
383 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
384 // closer to the diagonal and in same-sign quarters, change both using ratio
385 rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
386 rect->width.computed = MAX(w_orig - miny * ratio, 0);
387 } else {
388 // closer to the vertical, change only height, width is w_orig
389 rect->x.computed = MIN(origin[NR::X], opposite_x);
390 rect->width.computed = MAX(w_orig, 0);
391 }
393 }
395 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
397 } else {
398 // move freely
399 rect->x.computed = MIN(s[NR::X], opposite_x);
400 rect->width.computed = MAX(w_orig - minx, 0);
401 rect->y.computed = MIN(s[NR::Y], opposite_y);
402 rect->height.computed = MAX(h_orig - miny, 0);
403 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
404 }
406 sp_rect_clamp_radii(rect);
408 update_knot();
410 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
411 }
413 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
414 KnotHolder(desktop, item, relhandler)
415 {
416 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
417 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
418 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
419 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
420 entity_rx->create(desktop, item, this,
421 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
422 "to make the vertical radius the same"),
423 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
424 entity_ry->create(desktop, item, this,
425 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
426 "to make the horizontal radius the same"),
427 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
428 entity_wh->create(desktop, item, this,
429 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
430 "to lock ratio or stretch in one dimension only"),
431 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
432 entity_xy->create(desktop, item, this,
433 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
434 "to lock ratio or stretch in one dimension only"),
435 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
436 entity.push_back(entity_rx);
437 entity.push_back(entity_ry);
438 entity.push_back(entity_wh);
439 entity.push_back(entity_xy);
441 add_pattern_knotholder();
442 }
444 /* Box3D (= the new 3D box structure) */
446 class Box3DKnotHolderEntity : public KnotHolderEntity {
447 public:
448 virtual NR::Point knot_get() = 0;
449 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state) = 0;
451 static NR::Point knot_get_generic(SPItem *item, unsigned int knot_id);
452 static void knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &p, guint state);
453 };
455 NR::Point
456 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
457 {
458 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
459 }
461 void
462 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &new_pos, guint state)
463 {
464 NR::Point const s = snap_knot_position(item, new_pos);
466 g_assert(item != NULL);
467 SPBox3D *box = SP_BOX3D(item);
468 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
470 Box3D::Axis movement;
471 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
472 movement = Box3D::XY;
473 } else {
474 movement = Box3D::Z;
475 }
477 box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
478 box3d_set_z_orders(box);
479 box3d_position_set(box);
480 }
482 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
483 public:
484 virtual NR::Point knot_get();
485 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
486 };
488 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
489 public:
490 virtual NR::Point knot_get();
491 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
492 };
494 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
495 public:
496 virtual NR::Point knot_get();
497 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
498 };
500 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
501 public:
502 virtual NR::Point knot_get();
503 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
504 };
506 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
507 public:
508 virtual NR::Point knot_get();
509 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
510 };
512 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
513 public:
514 virtual NR::Point knot_get();
515 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
516 };
518 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
519 public:
520 virtual NR::Point knot_get();
521 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
522 };
524 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
525 public:
526 virtual NR::Point knot_get();
527 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
528 };
530 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
531 public:
532 virtual NR::Point knot_get();
533 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
534 };
536 NR::Point
537 Box3DKnotHolderEntity0::knot_get()
538 {
539 return knot_get_generic(item, 0);
540 }
542 NR::Point
543 Box3DKnotHolderEntity1::knot_get()
544 {
545 return knot_get_generic(item, 1);
546 }
548 NR::Point
549 Box3DKnotHolderEntity2::knot_get()
550 {
551 return knot_get_generic(item, 2);
552 }
554 NR::Point
555 Box3DKnotHolderEntity3::knot_get()
556 {
557 return knot_get_generic(item, 3);
558 }
560 NR::Point
561 Box3DKnotHolderEntity4::knot_get()
562 {
563 return knot_get_generic(item, 4);
564 }
566 NR::Point
567 Box3DKnotHolderEntity5::knot_get()
568 {
569 return knot_get_generic(item, 5);
570 }
572 NR::Point
573 Box3DKnotHolderEntity6::knot_get()
574 {
575 return knot_get_generic(item, 6);
576 }
578 NR::Point
579 Box3DKnotHolderEntity7::knot_get()
580 {
581 return knot_get_generic(item, 7);
582 }
584 NR::Point
585 Box3DKnotHolderEntityCenter::knot_get()
586 {
587 return box3d_get_center_screen(SP_BOX3D(item));
588 }
590 void
591 Box3DKnotHolderEntity0::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
592 {
593 knot_set_generic(item, 0, new_pos, state);
594 }
596 void
597 Box3DKnotHolderEntity1::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
598 {
599 knot_set_generic(item, 1, new_pos, state);
600 }
602 void
603 Box3DKnotHolderEntity2::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
604 {
605 knot_set_generic(item, 2, new_pos, state);
606 }
608 void
609 Box3DKnotHolderEntity3::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
610 {
611 knot_set_generic(item, 3, new_pos, state);
612 }
614 void
615 Box3DKnotHolderEntity4::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
616 {
617 knot_set_generic(item, 4, new_pos, state);
618 }
620 void
621 Box3DKnotHolderEntity5::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
622 {
623 knot_set_generic(item, 5, new_pos, state);
624 }
626 void
627 Box3DKnotHolderEntity6::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
628 {
629 knot_set_generic(item, 6, new_pos, state);
630 }
632 void
633 Box3DKnotHolderEntity7::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
634 {
635 knot_set_generic(item, 7, new_pos, state);
636 }
638 void
639 Box3DKnotHolderEntityCenter::knot_set(NR::Point const &new_pos, NR::Point const &origin, guint state)
640 {
641 NR::Point const s = snap_knot_position(item, new_pos);
643 SPBox3D *box = SP_BOX3D(item);
644 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
646 box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
647 state & GDK_CONTROL_MASK);
649 box3d_set_z_orders(box);
650 box3d_position_set(box);
651 }
653 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
654 KnotHolder(desktop, item, relhandler)
655 {
656 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
657 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
658 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
659 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
660 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
661 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
662 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
663 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
664 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
666 entity_corner0->create(desktop, item, this,
667 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
668 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
669 entity_corner1->create(desktop, item, this,
670 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
671 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
672 entity_corner2->create(desktop, item, this,
673 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
674 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
675 entity_corner3->create(desktop, item, this,
676 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
677 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
678 entity_corner4->create(desktop, item, this,
679 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
680 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
681 entity_corner5->create(desktop, item, this,
682 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
683 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
684 entity_corner6->create(desktop, item, this,
685 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
686 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
687 entity_corner7->create(desktop, item, this,
688 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
689 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
690 entity_center->create(desktop, item, this,
691 _("Move the box in perspective"),
692 SP_KNOT_SHAPE_CROSS);
694 entity.push_back(entity_corner0);
695 entity.push_back(entity_corner1);
696 entity.push_back(entity_corner2);
697 entity.push_back(entity_corner3);
698 entity.push_back(entity_corner4);
699 entity.push_back(entity_corner5);
700 entity.push_back(entity_corner6);
701 entity.push_back(entity_corner7);
702 entity.push_back(entity_center);
704 add_pattern_knotholder();
705 }
707 /* SPArc */
709 class ArcKnotHolderEntityStart : public KnotHolderEntity {
710 public:
711 virtual NR::Point knot_get();
712 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
713 };
715 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
716 public:
717 virtual NR::Point knot_get();
718 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
719 virtual void knot_click(guint state);
720 };
722 class ArcKnotHolderEntityRX : public KnotHolderEntity {
723 public:
724 virtual NR::Point knot_get();
725 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
726 virtual void knot_click(guint state);
727 };
729 class ArcKnotHolderEntityRY : public KnotHolderEntity {
730 public:
731 virtual NR::Point knot_get();
732 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
733 virtual void knot_click(guint state);
734 };
736 /*
737 * return values:
738 * 1 : inside
739 * 0 : on the curves
740 * -1 : outside
741 */
742 static gint
743 sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
744 {
745 gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
746 gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
748 gdouble s = dx * dx + dy * dy;
749 if (s < 1.0) return 1;
750 if (s > 1.0) return -1;
751 return 0;
752 }
754 void
755 ArcKnotHolderEntityStart::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
756 {
757 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
759 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
760 SPArc *arc = SP_ARC(item);
762 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
764 NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
765 NR::scale sc(ge->rx.computed, ge->ry.computed);
766 ge->start = atan2(delta * sc.inverse());
767 if ( ( state & GDK_CONTROL_MASK )
768 && snaps )
769 {
770 ge->start = sp_round(ge->start, M_PI/snaps);
771 }
772 sp_genericellipse_normalize(ge);
773 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
774 }
776 NR::Point
777 ArcKnotHolderEntityStart::knot_get()
778 {
779 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
780 SPArc *arc = SP_ARC(item);
782 return sp_arc_get_xy(arc, ge->start);
783 }
785 void
786 ArcKnotHolderEntityEnd::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
787 {
788 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
790 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
791 SPArc *arc = SP_ARC(item);
793 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
795 NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
796 NR::scale sc(ge->rx.computed, ge->ry.computed);
797 ge->end = atan2(delta * sc.inverse());
798 if ( ( state & GDK_CONTROL_MASK )
799 && snaps )
800 {
801 ge->end = sp_round(ge->end, M_PI/snaps);
802 }
803 sp_genericellipse_normalize(ge);
804 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
805 }
807 NR::Point
808 ArcKnotHolderEntityEnd::knot_get()
809 {
810 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
811 SPArc *arc = SP_ARC(item);
813 return sp_arc_get_xy(arc, ge->end);
814 }
817 void
818 ArcKnotHolderEntityEnd::knot_click(guint state)
819 {
820 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
822 if (state & GDK_SHIFT_MASK) {
823 ge->end = ge->start = 0;
824 ((SPObject *)ge)->updateRepr();
825 }
826 }
829 void
830 ArcKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
831 {
832 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
833 SPArc *arc = SP_ARC(item);
835 NR::Point const s = snap_knot_position(arc, p);
837 ge->rx.computed = fabs( ge->cx.computed - s[NR::X] );
839 if ( state & GDK_CONTROL_MASK ) {
840 ge->ry.computed = ge->rx.computed;
841 }
843 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
844 }
846 NR::Point
847 ArcKnotHolderEntityRX::knot_get()
848 {
849 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
851 return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(ge->rx.computed, 0));
852 }
854 void
855 ArcKnotHolderEntityRX::knot_click(guint state)
856 {
857 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
859 if (state & GDK_CONTROL_MASK) {
860 ge->ry.computed = ge->rx.computed;
861 ((SPObject *)ge)->updateRepr();
862 }
863 }
865 void
866 ArcKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
867 {
868 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
869 SPArc *arc = SP_ARC(item);
871 NR::Point const s = snap_knot_position(arc, p);
873 ge->ry.computed = fabs( ge->cy.computed - s[NR::Y] );
875 if ( state & GDK_CONTROL_MASK ) {
876 ge->rx.computed = ge->ry.computed;
877 }
879 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
880 }
882 NR::Point
883 ArcKnotHolderEntityRY::knot_get()
884 {
885 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
887 return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(0, ge->ry.computed));
888 }
890 void
891 ArcKnotHolderEntityRY::knot_click(guint state)
892 {
893 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
895 if (state & GDK_CONTROL_MASK) {
896 ge->rx.computed = ge->ry.computed;
897 ((SPObject *)ge)->updateRepr();
898 }
899 }
901 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
902 KnotHolder(desktop, item, relhandler)
903 {
904 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
905 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
906 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
907 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
908 entity_rx->create(desktop, item, this,
909 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
910 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
911 entity_ry->create(desktop, item, this,
912 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
913 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
914 entity_start->create(desktop, item, this,
915 _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
916 "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
917 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
918 entity_end->create(desktop, item, this,
919 _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
920 "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
921 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
922 entity.push_back(entity_rx);
923 entity.push_back(entity_ry);
924 entity.push_back(entity_start);
925 entity.push_back(entity_end);
927 add_pattern_knotholder();
928 }
930 /* SPStar */
932 class StarKnotHolderEntity1 : public KnotHolderEntity {
933 public:
934 virtual NR::Point knot_get();
935 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
936 virtual void knot_click(guint state);
937 };
939 class StarKnotHolderEntity2 : public KnotHolderEntity {
940 public:
941 virtual NR::Point knot_get();
942 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
943 virtual void knot_click(guint state);
944 };
946 void
947 StarKnotHolderEntity1::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
948 {
949 SPStar *star = SP_STAR(item);
951 NR::Point const s = snap_knot_position(star, p);
953 NR::Point d = s - star->center;
955 double arg1 = atan2(d);
956 double darg1 = arg1 - star->arg[0];
958 if (state & GDK_MOD1_MASK) {
959 star->randomized = darg1/(star->arg[0] - star->arg[1]);
960 } else if (state & GDK_SHIFT_MASK) {
961 star->rounded = darg1/(star->arg[0] - star->arg[1]);
962 } else if (state & GDK_CONTROL_MASK) {
963 star->r[0] = L2(d);
964 } else {
965 star->r[0] = L2(d);
966 star->arg[0] = arg1;
967 star->arg[1] += darg1;
968 }
969 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
970 }
972 void
973 StarKnotHolderEntity2::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
974 {
975 SPStar *star = SP_STAR(item);
977 NR::Point const s = snap_knot_position(star, p);
979 if (star->flatsided == false) {
980 NR::Point d = s - star->center;
982 double arg1 = atan2(d);
983 double darg1 = arg1 - star->arg[1];
985 if (state & GDK_MOD1_MASK) {
986 star->randomized = darg1/(star->arg[0] - star->arg[1]);
987 } else if (state & GDK_SHIFT_MASK) {
988 star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
989 } else if (state & GDK_CONTROL_MASK) {
990 star->r[1] = L2(d);
991 star->arg[1] = star->arg[0] + M_PI / star->sides;
992 }
993 else {
994 star->r[1] = L2(d);
995 star->arg[1] = atan2(d);
996 }
997 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
998 }
999 }
1001 NR::Point
1002 StarKnotHolderEntity1::knot_get()
1003 {
1004 g_assert(item != NULL);
1006 SPStar *star = SP_STAR(item);
1008 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1010 }
1012 NR::Point
1013 StarKnotHolderEntity2::knot_get()
1014 {
1015 g_assert(item != NULL);
1017 SPStar *star = SP_STAR(item);
1019 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1020 }
1022 static void
1023 sp_star_knot_click(SPItem *item, guint state)
1024 {
1025 SPStar *star = SP_STAR(item);
1027 if (state & GDK_MOD1_MASK) {
1028 star->randomized = 0;
1029 ((SPObject *)star)->updateRepr();
1030 } else if (state & GDK_SHIFT_MASK) {
1031 star->rounded = 0;
1032 ((SPObject *)star)->updateRepr();
1033 } else if (state & GDK_CONTROL_MASK) {
1034 star->arg[1] = star->arg[0] + M_PI / star->sides;
1035 ((SPObject *)star)->updateRepr();
1036 }
1037 }
1039 void
1040 StarKnotHolderEntity1::knot_click(guint state)
1041 {
1042 return sp_star_knot_click(item, state);
1043 }
1045 void
1046 StarKnotHolderEntity2::knot_click(guint state)
1047 {
1048 return sp_star_knot_click(item, state);
1049 }
1051 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1052 KnotHolder(desktop, item, relhandler)
1053 {
1054 SPStar *star = SP_STAR(item);
1056 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1057 entity1->create(desktop, item, this,
1058 _("Adjust the <b>tip radius</b> of the star or polygon; "
1059 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1060 entity.push_back(entity1);
1062 if (star->flatsided == false) {
1063 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1064 entity2->create(desktop, item, this,
1065 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1066 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1067 entity.push_back(entity2);
1068 }
1070 add_pattern_knotholder();
1071 }
1073 /* SPSpiral */
1075 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1076 public:
1077 virtual NR::Point knot_get();
1078 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1079 virtual void knot_click(guint state);
1080 };
1082 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1083 public:
1084 virtual NR::Point knot_get();
1085 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1086 };
1089 /*
1090 * set attributes via inner (t=t0) knot point:
1091 * [default] increase/decrease inner point
1092 * [shift] increase/decrease inner and outer arg synchronizely
1093 * [control] constrain inner arg to round per PI/4
1094 */
1095 void
1096 SpiralKnotHolderEntityInner::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1097 {
1098 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1100 SPSpiral *spiral = SP_SPIRAL(item);
1102 gdouble dx = p[NR::X] - spiral->cx;
1103 gdouble dy = p[NR::Y] - spiral->cy;
1105 if (state & GDK_MOD1_MASK) {
1106 // adjust divergence by vertical drag, relative to rad
1107 double new_exp = (spiral->rad + dy)/(spiral->rad);
1108 spiral->exp = new_exp > 0? new_exp : 0;
1109 } else {
1110 // roll/unroll from inside
1111 gdouble arg_t0;
1112 sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1114 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1115 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1116 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1118 /* round inner arg per PI/snaps, if CTRL is pressed */
1119 if ( ( state & GDK_CONTROL_MASK )
1120 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1121 && ( snaps != 0 ) ) {
1122 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1123 spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1124 }
1126 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1127 }
1129 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1130 }
1132 /*
1133 * set attributes via outer (t=1) knot point:
1134 * [default] increase/decrease revolution factor
1135 * [control] constrain inner arg to round per PI/4
1136 */
1137 void
1138 SpiralKnotHolderEntityOuter::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1139 {
1140 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1142 SPSpiral *spiral = SP_SPIRAL(item);
1144 gdouble dx = p[NR::X] - spiral->cx;
1145 gdouble dy = p[NR::Y] - spiral->cy;
1147 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1148 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1149 if (!(state & GDK_MOD1_MASK)) {
1150 // if alt not pressed, change also rad; otherwise it is locked
1151 spiral->rad = MAX(hypot(dx, dy), 0.001);
1152 }
1153 if ( ( state & GDK_CONTROL_MASK )
1154 && snaps ) {
1155 spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1156 }
1157 } else { // roll/unroll
1158 // arg of the spiral outer end
1159 double arg_1;
1160 sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1162 // its fractional part after the whole turns are subtracted
1163 double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1165 // arg of the mouse point relative to spiral center
1166 double mouse_angle = atan2(dy, dx);
1167 if (mouse_angle < 0)
1168 mouse_angle += 2*M_PI;
1170 // snap if ctrl
1171 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1172 mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1173 }
1175 // by how much we want to rotate the outer point
1176 double diff = mouse_angle - arg_r;
1177 if (diff > M_PI)
1178 diff -= 2*M_PI;
1179 else if (diff < -M_PI)
1180 diff += 2*M_PI;
1182 // calculate the new rad;
1183 // the value of t corresponding to the angle arg_1 + diff:
1184 double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1185 // the rad at that t:
1186 double rad_new = 0;
1187 if (t_temp > spiral->t0)
1188 sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1190 // change the revo (converting diff from radians to the number of turns)
1191 spiral->revo += diff/(2*M_PI);
1192 if (spiral->revo < 1e-3)
1193 spiral->revo = 1e-3;
1195 // if alt not pressed and the values are sane, change the rad
1196 if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1197 // adjust t0 too so that the inner point stays unmoved
1198 double r0;
1199 sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1200 spiral->rad = rad_new;
1201 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1202 }
1203 if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1204 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1205 }
1207 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1208 }
1210 NR::Point
1211 SpiralKnotHolderEntityInner::knot_get()
1212 {
1213 SPSpiral *spiral = SP_SPIRAL(item);
1215 return sp_spiral_get_xy(spiral, spiral->t0);
1216 }
1218 NR::Point
1219 SpiralKnotHolderEntityOuter::knot_get()
1220 {
1221 SPSpiral *spiral = SP_SPIRAL(item);
1223 return sp_spiral_get_xy(spiral, 1.0);
1224 }
1226 void
1227 SpiralKnotHolderEntityInner::knot_click(guint state)
1228 {
1229 SPSpiral *spiral = SP_SPIRAL(item);
1231 if (state & GDK_MOD1_MASK) {
1232 spiral->exp = 1;
1233 ((SPObject *)spiral)->updateRepr();
1234 } else if (state & GDK_SHIFT_MASK) {
1235 spiral->t0 = 0;
1236 ((SPObject *)spiral)->updateRepr();
1237 }
1238 }
1240 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1241 KnotHolder(desktop, item, relhandler)
1242 {
1243 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1244 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1245 entity_inner->create(desktop, item, this,
1246 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1247 "with <b>Alt</b> to converge/diverge"));
1248 entity_outer->create(desktop, item, this,
1249 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1250 "with <b>Shift</b> to scale/rotate"));
1251 entity.push_back(entity_inner);
1252 entity.push_back(entity_outer);
1254 add_pattern_knotholder();
1255 }
1257 /* SPOffset */
1259 class OffsetKnotHolderEntity : public KnotHolderEntity {
1260 public:
1261 virtual NR::Point knot_get();
1262 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1263 };
1265 void
1266 OffsetKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
1267 {
1268 SPOffset *offset = SP_OFFSET(item);
1270 offset->rad = sp_offset_distance_to_original(offset, p);
1271 offset->knot = p;
1272 offset->knotSet = true;
1274 ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1275 }
1278 NR::Point
1279 OffsetKnotHolderEntity::knot_get()
1280 {
1281 SPOffset *offset = SP_OFFSET(item);
1283 NR::Point np;
1284 sp_offset_top_point(offset,&np);
1285 return np;
1286 }
1288 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1289 KnotHolder(desktop, item, relhandler)
1290 {
1291 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1292 entity_offset->create(desktop, item, this,
1293 _("Adjust the <b>offset distance</b>"));
1294 entity.push_back(entity_offset);
1296 add_pattern_knotholder();
1297 }
1299 class FlowtextKnotHolderEntity : public KnotHolderEntity {
1300 public:
1301 virtual NR::Point knot_get();
1302 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1303 };
1305 NR::Point
1306 FlowtextKnotHolderEntity::knot_get()
1307 {
1308 SPRect *rect = SP_RECT(item);
1310 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1311 }
1313 void
1314 FlowtextKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
1315 {
1316 SPRect *rect = SP_RECT(item);
1318 sp_rect_wh_set_internal(rect, p, origin, state);
1319 }
1321 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1322 KnotHolder(desktop, item, relhandler)
1323 {
1324 g_assert(item != NULL);
1326 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1327 entity_flowtext->create(desktop, item, this,
1328 _("Drag to resize the <b>flowed text frame</b>"));
1329 entity.push_back(entity_flowtext);
1330 }
1332 /*
1333 Local Variables:
1334 mode:c++
1335 c-file-style:"stroustrup"
1336 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1337 indent-tabs-mode:nil
1338 fill-column:99
1339 End:
1340 */
1341 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :