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->add_pattern_knotholder();
92 }
94 return knotholder;
95 }
97 /* SPRect */
99 /* handle for horizontal rounding radius */
100 class RectKnotHolderEntityRX : public KnotHolderEntity {
101 public:
102 virtual NR::Point knot_get();
103 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
104 virtual void knot_click(guint state);
105 };
107 /* handle for vertical rounding radius */
108 class RectKnotHolderEntityRY : public KnotHolderEntity {
109 public:
110 virtual NR::Point knot_get();
111 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
112 virtual void knot_click(guint state);
113 };
115 /* handle for width/height adjustment */
116 class RectKnotHolderEntityWH : public KnotHolderEntity {
117 public:
118 virtual NR::Point knot_get();
119 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
120 };
122 /* handle for x/y adjustment */
123 class RectKnotHolderEntityXY : public KnotHolderEntity {
124 public:
125 virtual NR::Point knot_get();
126 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
127 };
129 static NR::Point snap_knot_position(SPItem *item, NR::Point const &p)
130 {
131 SPDesktop const *desktop = inkscape_active_desktop();
132 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
133 NR::Point s = p * i2d;
134 SnapManager &m = desktop->namedview->snap_manager;
135 m.setup(desktop, item);
136 m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, s);
137 return s * i2d.inverse();
138 }
140 NR::Point
141 RectKnotHolderEntityRX::knot_get()
142 {
143 SPRect *rect = SP_RECT(item);
145 return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
146 }
148 void
149 RectKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
150 {
151 SPRect *rect = SP_RECT(item);
153 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
154 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
155 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
157 if (state & GDK_CONTROL_MASK) {
158 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
159 rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
160 rect->rx._set = rect->ry._set = true;
162 } else {
163 rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
164 rect->rx._set = true;
165 }
167 update_knot();
169 ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
170 }
172 void
173 RectKnotHolderEntityRX::knot_click(guint state)
174 {
175 SPRect *rect = SP_RECT(item);
177 if (state & GDK_SHIFT_MASK) {
178 /* remove rounding from rectangle */
179 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
180 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
181 } else if (state & GDK_CONTROL_MASK) {
182 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
183 SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
184 }
186 update_knot();
187 }
189 NR::Point
190 RectKnotHolderEntityRY::knot_get()
191 {
192 SPRect *rect = SP_RECT(item);
194 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
195 }
197 void
198 RectKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
199 {
200 SPRect *rect = SP_RECT(item);
202 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
203 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
204 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
206 if (state & GDK_CONTROL_MASK) {
207 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
208 rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
209 rect->ry._set = rect->rx._set = true;
210 } else {
211 if (!rect->rx._set || rect->rx.computed == 0) {
212 rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
213 0.0,
214 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
215 } else {
216 rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
217 0.0,
218 rect->height.computed / 2.0);
219 }
221 rect->ry._set = true;
222 }
224 update_knot();
226 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
227 }
229 void
230 RectKnotHolderEntityRY::knot_click(guint state)
231 {
232 SPRect *rect = SP_RECT(item);
234 if (state & GDK_SHIFT_MASK) {
235 /* remove rounding */
236 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
237 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
238 } else if (state & GDK_CONTROL_MASK) {
239 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
240 SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
241 }
242 }
244 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
246 static void sp_rect_clamp_radii(SPRect *rect)
247 {
248 // clamp rounding radii so that they do not exceed width/height
249 if (2 * rect->rx.computed > rect->width.computed) {
250 rect->rx.computed = 0.5 * rect->width.computed;
251 rect->rx._set = true;
252 }
253 if (2 * rect->ry.computed > rect->height.computed) {
254 rect->ry.computed = 0.5 * rect->height.computed;
255 rect->ry._set = true;
256 }
257 }
259 NR::Point
260 RectKnotHolderEntityWH::knot_get()
261 {
262 SPRect *rect = SP_RECT(item);
264 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
265 }
267 static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
268 {
269 NR::Point const s = snap_knot_position(rect, p);
271 if (state & GDK_CONTROL_MASK) {
272 // original width/height when drag started
273 gdouble const w_orig = (origin[NR::X] - rect->x.computed);
274 gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
276 //original ratio
277 gdouble const ratio = (w_orig / h_orig);
279 // mouse displacement since drag started
280 gdouble const minx = s[NR::X] - origin[NR::X];
281 gdouble const miny = s[NR::Y] - origin[NR::Y];
283 if (fabs(minx) > fabs(miny)) {
285 // snap to horizontal or diagonal
286 rect->width.computed = MAX(w_orig + minx, 0);
287 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
288 // closer to the diagonal and in same-sign quarters, change both using ratio
289 rect->height.computed = MAX(h_orig + minx / ratio, 0);
290 } else {
291 // closer to the horizontal, change only width, height is h_orig
292 rect->height.computed = MAX(h_orig, 0);
293 }
295 } else {
296 // snap to vertical or diagonal
297 rect->height.computed = MAX(h_orig + miny, 0);
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 rect->width.computed = MAX(w_orig + miny * ratio, 0);
301 } else {
302 // closer to the vertical, change only height, width is w_orig
303 rect->width.computed = MAX(w_orig, 0);
304 }
305 }
307 rect->width._set = rect->height._set = true;
309 } else {
310 // move freely
311 rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
312 rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
313 rect->width._set = rect->height._set = true;
314 }
316 sp_rect_clamp_radii(rect);
318 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
319 }
321 void
322 RectKnotHolderEntityWH::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
323 {
324 SPRect *rect = SP_RECT(item);
326 sp_rect_wh_set_internal(rect, p, origin, state);
328 update_knot();
329 }
331 NR::Point
332 RectKnotHolderEntityXY::knot_get()
333 {
334 SPRect *rect = SP_RECT(item);
336 return NR::Point(rect->x.computed, rect->y.computed);
337 }
339 void
340 RectKnotHolderEntityXY::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
341 {
342 SPRect *rect = SP_RECT(item);
344 // opposite corner (unmoved)
345 gdouble opposite_x = (rect->x.computed + rect->width.computed);
346 gdouble opposite_y = (rect->y.computed + rect->height.computed);
348 // original width/height when drag started
349 gdouble w_orig = opposite_x - origin[NR::X];
350 gdouble h_orig = opposite_y - origin[NR::Y];
352 NR::Point const s = snap_knot_position(rect, p);
354 // mouse displacement since drag started
355 gdouble minx = s[NR::X] - origin[NR::X];
356 gdouble miny = s[NR::Y] - origin[NR::Y];
358 if (state & GDK_CONTROL_MASK) {
359 //original ratio
360 gdouble ratio = (w_orig / h_orig);
362 if (fabs(minx) > fabs(miny)) {
364 // snap to horizontal or diagonal
365 rect->x.computed = MIN(s[NR::X], opposite_x);
366 rect->width.computed = MAX(w_orig - minx, 0);
367 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
368 // closer to the diagonal and in same-sign quarters, change both using ratio
369 rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
370 rect->height.computed = MAX(h_orig - minx / ratio, 0);
371 } else {
372 // closer to the horizontal, change only width, height is h_orig
373 rect->y.computed = MIN(origin[NR::Y], opposite_y);
374 rect->height.computed = MAX(h_orig, 0);
375 }
377 } else {
379 // snap to vertical or diagonal
380 rect->y.computed = MIN(s[NR::Y], opposite_y);
381 rect->height.computed = MAX(h_orig - miny, 0);
382 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
383 // closer to the diagonal and in same-sign quarters, change both using ratio
384 rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
385 rect->width.computed = MAX(w_orig - miny * ratio, 0);
386 } else {
387 // closer to the vertical, change only height, width is w_orig
388 rect->x.computed = MIN(origin[NR::X], opposite_x);
389 rect->width.computed = MAX(w_orig, 0);
390 }
392 }
394 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
396 } else {
397 // move freely
398 rect->x.computed = MIN(s[NR::X], opposite_x);
399 rect->width.computed = MAX(w_orig - minx, 0);
400 rect->y.computed = MIN(s[NR::Y], opposite_y);
401 rect->height.computed = MAX(h_orig - miny, 0);
402 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
403 }
405 sp_rect_clamp_radii(rect);
407 update_knot();
409 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
410 }
412 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
413 KnotHolder(desktop, item, relhandler)
414 {
415 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
416 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
417 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
418 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
419 entity_rx->create(desktop, item, this,
420 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
421 "to make the vertical radius the same"),
422 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
423 entity_ry->create(desktop, item, this,
424 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
425 "to make the horizontal radius the same"),
426 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
427 entity_wh->create(desktop, item, this,
428 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
429 "to lock ratio or stretch in one dimension only"),
430 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
431 entity_xy->create(desktop, item, this,
432 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
433 "to lock ratio or stretch in one dimension only"),
434 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
435 entity.push_back(entity_rx);
436 entity.push_back(entity_ry);
437 entity.push_back(entity_wh);
438 entity.push_back(entity_xy);
440 add_pattern_knotholder();
441 }
443 /* Box3D (= the new 3D box structure) */
445 class Box3DKnotHolderEntity : public KnotHolderEntity {
446 public:
447 virtual NR::Point knot_get() = 0;
448 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state) = 0;
450 static NR::Point knot_get_generic(SPItem *item, unsigned int knot_id);
451 static void knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &p, guint state);
452 };
454 NR::Point
455 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
456 {
457 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
458 }
460 void
461 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &new_pos, guint state)
462 {
463 NR::Point const s = snap_knot_position(item, new_pos);
465 g_assert(item != NULL);
466 SPBox3D *box = SP_BOX3D(item);
467 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
469 Box3D::Axis movement;
470 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
471 movement = Box3D::XY;
472 } else {
473 movement = Box3D::Z;
474 }
476 box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
477 box3d_set_z_orders(box);
478 box3d_position_set(box);
479 }
481 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
482 public:
483 virtual NR::Point knot_get();
484 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
485 };
487 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
488 public:
489 virtual NR::Point knot_get();
490 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
491 };
493 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
494 public:
495 virtual NR::Point knot_get();
496 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
497 };
499 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
500 public:
501 virtual NR::Point knot_get();
502 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
503 };
505 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
506 public:
507 virtual NR::Point knot_get();
508 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
509 };
511 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
512 public:
513 virtual NR::Point knot_get();
514 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
515 };
517 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
518 public:
519 virtual NR::Point knot_get();
520 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
521 };
523 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
524 public:
525 virtual NR::Point knot_get();
526 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
527 };
529 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
530 public:
531 virtual NR::Point knot_get();
532 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
533 };
535 NR::Point
536 Box3DKnotHolderEntity0::knot_get()
537 {
538 return knot_get_generic(item, 0);
539 }
541 NR::Point
542 Box3DKnotHolderEntity1::knot_get()
543 {
544 return knot_get_generic(item, 1);
545 }
547 NR::Point
548 Box3DKnotHolderEntity2::knot_get()
549 {
550 return knot_get_generic(item, 2);
551 }
553 NR::Point
554 Box3DKnotHolderEntity3::knot_get()
555 {
556 return knot_get_generic(item, 3);
557 }
559 NR::Point
560 Box3DKnotHolderEntity4::knot_get()
561 {
562 return knot_get_generic(item, 4);
563 }
565 NR::Point
566 Box3DKnotHolderEntity5::knot_get()
567 {
568 return knot_get_generic(item, 5);
569 }
571 NR::Point
572 Box3DKnotHolderEntity6::knot_get()
573 {
574 return knot_get_generic(item, 6);
575 }
577 NR::Point
578 Box3DKnotHolderEntity7::knot_get()
579 {
580 return knot_get_generic(item, 7);
581 }
583 NR::Point
584 Box3DKnotHolderEntityCenter::knot_get()
585 {
586 return box3d_get_center_screen(SP_BOX3D(item));
587 }
589 void
590 Box3DKnotHolderEntity0::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
591 {
592 knot_set_generic(item, 0, new_pos, state);
593 }
595 void
596 Box3DKnotHolderEntity1::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
597 {
598 knot_set_generic(item, 1, new_pos, state);
599 }
601 void
602 Box3DKnotHolderEntity2::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
603 {
604 knot_set_generic(item, 2, new_pos, state);
605 }
607 void
608 Box3DKnotHolderEntity3::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
609 {
610 knot_set_generic(item, 3, new_pos, state);
611 }
613 void
614 Box3DKnotHolderEntity4::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
615 {
616 knot_set_generic(item, 4, new_pos, state);
617 }
619 void
620 Box3DKnotHolderEntity5::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
621 {
622 knot_set_generic(item, 5, new_pos, state);
623 }
625 void
626 Box3DKnotHolderEntity6::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
627 {
628 knot_set_generic(item, 6, new_pos, state);
629 }
631 void
632 Box3DKnotHolderEntity7::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
633 {
634 knot_set_generic(item, 7, new_pos, state);
635 }
637 void
638 Box3DKnotHolderEntityCenter::knot_set(NR::Point const &new_pos, NR::Point const &origin, guint state)
639 {
640 NR::Point const s = snap_knot_position(item, new_pos);
642 SPBox3D *box = SP_BOX3D(item);
643 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
645 box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
646 state & GDK_CONTROL_MASK);
648 box3d_set_z_orders(box);
649 box3d_position_set(box);
650 }
652 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
653 KnotHolder(desktop, item, relhandler)
654 {
655 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
656 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
657 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
658 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
659 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
660 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
661 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
662 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
663 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
665 entity_corner0->create(desktop, item, this,
666 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
667 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
668 entity_corner1->create(desktop, item, this,
669 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
670 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
671 entity_corner2->create(desktop, item, this,
672 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
673 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
674 entity_corner3->create(desktop, item, this,
675 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
676 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
677 entity_corner4->create(desktop, item, this,
678 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
679 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
680 entity_corner5->create(desktop, item, this,
681 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
682 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
683 entity_corner6->create(desktop, item, this,
684 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
685 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
686 entity_corner7->create(desktop, item, this,
687 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
688 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
689 entity_center->create(desktop, item, this,
690 _("Move the box in perspective"),
691 SP_KNOT_SHAPE_CROSS);
693 entity.push_back(entity_corner0);
694 entity.push_back(entity_corner1);
695 entity.push_back(entity_corner2);
696 entity.push_back(entity_corner3);
697 entity.push_back(entity_corner4);
698 entity.push_back(entity_corner5);
699 entity.push_back(entity_corner6);
700 entity.push_back(entity_corner7);
701 entity.push_back(entity_center);
703 add_pattern_knotholder();
704 }
706 /* SPArc */
708 class ArcKnotHolderEntityStart : public KnotHolderEntity {
709 public:
710 virtual NR::Point knot_get();
711 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
712 };
714 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
715 public:
716 virtual NR::Point knot_get();
717 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
718 virtual void knot_click(guint state);
719 };
721 class ArcKnotHolderEntityRX : public KnotHolderEntity {
722 public:
723 virtual NR::Point knot_get();
724 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
725 virtual void knot_click(guint state);
726 };
728 class ArcKnotHolderEntityRY : public KnotHolderEntity {
729 public:
730 virtual NR::Point knot_get();
731 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
732 virtual void knot_click(guint state);
733 };
735 /*
736 * return values:
737 * 1 : inside
738 * 0 : on the curves
739 * -1 : outside
740 */
741 static gint
742 sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
743 {
744 gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
745 gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
747 gdouble s = dx * dx + dy * dy;
748 if (s < 1.0) return 1;
749 if (s > 1.0) return -1;
750 return 0;
751 }
753 void
754 ArcKnotHolderEntityStart::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
755 {
756 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
758 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
759 SPArc *arc = SP_ARC(item);
761 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
763 NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
764 NR::scale sc(ge->rx.computed, ge->ry.computed);
765 ge->start = atan2(delta * sc.inverse());
766 if ( ( state & GDK_CONTROL_MASK )
767 && snaps )
768 {
769 ge->start = sp_round(ge->start, M_PI/snaps);
770 }
771 sp_genericellipse_normalize(ge);
772 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
773 }
775 NR::Point
776 ArcKnotHolderEntityStart::knot_get()
777 {
778 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
779 SPArc *arc = SP_ARC(item);
781 return sp_arc_get_xy(arc, ge->start);
782 }
784 void
785 ArcKnotHolderEntityEnd::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
786 {
787 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
789 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
790 SPArc *arc = SP_ARC(item);
792 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
794 NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
795 NR::scale sc(ge->rx.computed, ge->ry.computed);
796 ge->end = atan2(delta * sc.inverse());
797 if ( ( state & GDK_CONTROL_MASK )
798 && snaps )
799 {
800 ge->end = sp_round(ge->end, M_PI/snaps);
801 }
802 sp_genericellipse_normalize(ge);
803 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
804 }
806 NR::Point
807 ArcKnotHolderEntityEnd::knot_get()
808 {
809 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
810 SPArc *arc = SP_ARC(item);
812 return sp_arc_get_xy(arc, ge->end);
813 }
816 void
817 ArcKnotHolderEntityEnd::knot_click(guint state)
818 {
819 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
821 if (state & GDK_SHIFT_MASK) {
822 ge->end = ge->start = 0;
823 ((SPObject *)ge)->updateRepr();
824 }
825 }
828 void
829 ArcKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
830 {
831 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
832 SPArc *arc = SP_ARC(item);
834 NR::Point const s = snap_knot_position(arc, p);
836 ge->rx.computed = fabs( ge->cx.computed - s[NR::X] );
838 if ( state & GDK_CONTROL_MASK ) {
839 ge->ry.computed = ge->rx.computed;
840 }
842 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
843 }
845 NR::Point
846 ArcKnotHolderEntityRX::knot_get()
847 {
848 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
850 return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(ge->rx.computed, 0));
851 }
853 void
854 ArcKnotHolderEntityRX::knot_click(guint state)
855 {
856 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
858 if (state & GDK_CONTROL_MASK) {
859 ge->ry.computed = ge->rx.computed;
860 ((SPObject *)ge)->updateRepr();
861 }
862 }
864 void
865 ArcKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
866 {
867 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
868 SPArc *arc = SP_ARC(item);
870 NR::Point const s = snap_knot_position(arc, p);
872 ge->ry.computed = fabs( ge->cy.computed - s[NR::Y] );
874 if ( state & GDK_CONTROL_MASK ) {
875 ge->rx.computed = ge->ry.computed;
876 }
878 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
879 }
881 NR::Point
882 ArcKnotHolderEntityRY::knot_get()
883 {
884 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
886 return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(0, ge->ry.computed));
887 }
889 void
890 ArcKnotHolderEntityRY::knot_click(guint state)
891 {
892 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
894 if (state & GDK_CONTROL_MASK) {
895 ge->rx.computed = ge->ry.computed;
896 ((SPObject *)ge)->updateRepr();
897 }
898 }
900 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
901 KnotHolder(desktop, item, relhandler)
902 {
903 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
904 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
905 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
906 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
907 entity_rx->create(desktop, item, this,
908 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
909 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
910 entity_ry->create(desktop, item, this,
911 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
912 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
913 entity_start->create(desktop, item, this,
914 _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
915 "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
916 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
917 entity_end->create(desktop, item, this,
918 _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
919 "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
920 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
921 entity.push_back(entity_rx);
922 entity.push_back(entity_ry);
923 entity.push_back(entity_start);
924 entity.push_back(entity_end);
926 add_pattern_knotholder();
927 }
929 /* SPStar */
931 class StarKnotHolderEntity1 : public KnotHolderEntity {
932 public:
933 virtual NR::Point knot_get();
934 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
935 virtual void knot_click(guint state);
936 };
938 class StarKnotHolderEntity2 : public KnotHolderEntity {
939 public:
940 virtual NR::Point knot_get();
941 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
942 virtual void knot_click(guint state);
943 };
945 void
946 StarKnotHolderEntity1::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
947 {
948 SPStar *star = SP_STAR(item);
950 NR::Point const s = snap_knot_position(star, p);
952 NR::Point d = s - star->center;
954 double arg1 = atan2(d);
955 double darg1 = arg1 - star->arg[0];
957 if (state & GDK_MOD1_MASK) {
958 star->randomized = darg1/(star->arg[0] - star->arg[1]);
959 } else if (state & GDK_SHIFT_MASK) {
960 star->rounded = darg1/(star->arg[0] - star->arg[1]);
961 } else if (state & GDK_CONTROL_MASK) {
962 star->r[0] = L2(d);
963 } else {
964 star->r[0] = L2(d);
965 star->arg[0] = arg1;
966 star->arg[1] += darg1;
967 }
968 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
969 }
971 void
972 StarKnotHolderEntity2::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
973 {
974 SPStar *star = SP_STAR(item);
976 NR::Point const s = snap_knot_position(star, p);
978 if (star->flatsided == false) {
979 NR::Point d = s - star->center;
981 double arg1 = atan2(d);
982 double darg1 = arg1 - star->arg[1];
984 if (state & GDK_MOD1_MASK) {
985 star->randomized = darg1/(star->arg[0] - star->arg[1]);
986 } else if (state & GDK_SHIFT_MASK) {
987 star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
988 } else if (state & GDK_CONTROL_MASK) {
989 star->r[1] = L2(d);
990 star->arg[1] = star->arg[0] + M_PI / star->sides;
991 }
992 else {
993 star->r[1] = L2(d);
994 star->arg[1] = atan2(d);
995 }
996 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
997 }
998 }
1000 NR::Point
1001 StarKnotHolderEntity1::knot_get()
1002 {
1003 g_assert(item != NULL);
1005 SPStar *star = SP_STAR(item);
1007 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1009 }
1011 NR::Point
1012 StarKnotHolderEntity2::knot_get()
1013 {
1014 g_assert(item != NULL);
1016 SPStar *star = SP_STAR(item);
1018 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1019 }
1021 static void
1022 sp_star_knot_click(SPItem *item, guint state)
1023 {
1024 SPStar *star = SP_STAR(item);
1026 if (state & GDK_MOD1_MASK) {
1027 star->randomized = 0;
1028 ((SPObject *)star)->updateRepr();
1029 } else if (state & GDK_SHIFT_MASK) {
1030 star->rounded = 0;
1031 ((SPObject *)star)->updateRepr();
1032 } else if (state & GDK_CONTROL_MASK) {
1033 star->arg[1] = star->arg[0] + M_PI / star->sides;
1034 ((SPObject *)star)->updateRepr();
1035 }
1036 }
1038 void
1039 StarKnotHolderEntity1::knot_click(guint state)
1040 {
1041 return sp_star_knot_click(item, state);
1042 }
1044 void
1045 StarKnotHolderEntity2::knot_click(guint state)
1046 {
1047 return sp_star_knot_click(item, state);
1048 }
1050 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1051 KnotHolder(desktop, item, relhandler)
1052 {
1053 SPStar *star = SP_STAR(item);
1055 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1056 entity1->create(desktop, item, this,
1057 _("Adjust the <b>tip radius</b> of the star or polygon; "
1058 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1059 entity.push_back(entity1);
1061 if (star->flatsided == false) {
1062 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1063 entity2->create(desktop, item, this,
1064 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1065 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1066 entity.push_back(entity2);
1067 }
1069 add_pattern_knotholder();
1070 }
1072 /* SPSpiral */
1074 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1075 public:
1076 virtual NR::Point knot_get();
1077 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1078 virtual void knot_click(guint state);
1079 };
1081 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1082 public:
1083 virtual NR::Point knot_get();
1084 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1085 };
1088 /*
1089 * set attributes via inner (t=t0) knot point:
1090 * [default] increase/decrease inner point
1091 * [shift] increase/decrease inner and outer arg synchronizely
1092 * [control] constrain inner arg to round per PI/4
1093 */
1094 void
1095 SpiralKnotHolderEntityInner::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1096 {
1097 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1099 SPSpiral *spiral = SP_SPIRAL(item);
1101 gdouble dx = p[NR::X] - spiral->cx;
1102 gdouble dy = p[NR::Y] - spiral->cy;
1104 if (state & GDK_MOD1_MASK) {
1105 // adjust divergence by vertical drag, relative to rad
1106 double new_exp = (spiral->rad + dy)/(spiral->rad);
1107 spiral->exp = new_exp > 0? new_exp : 0;
1108 } else {
1109 // roll/unroll from inside
1110 gdouble arg_t0;
1111 sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1113 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1114 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1115 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1117 /* round inner arg per PI/snaps, if CTRL is pressed */
1118 if ( ( state & GDK_CONTROL_MASK )
1119 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1120 && ( snaps != 0 ) ) {
1121 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1122 spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1123 }
1125 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1126 }
1128 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1129 }
1131 /*
1132 * set attributes via outer (t=1) knot point:
1133 * [default] increase/decrease revolution factor
1134 * [control] constrain inner arg to round per PI/4
1135 */
1136 void
1137 SpiralKnotHolderEntityOuter::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1138 {
1139 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1141 SPSpiral *spiral = SP_SPIRAL(item);
1143 gdouble dx = p[NR::X] - spiral->cx;
1144 gdouble dy = p[NR::Y] - spiral->cy;
1146 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1147 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1148 if (!(state & GDK_MOD1_MASK)) {
1149 // if alt not pressed, change also rad; otherwise it is locked
1150 spiral->rad = MAX(hypot(dx, dy), 0.001);
1151 }
1152 if ( ( state & GDK_CONTROL_MASK )
1153 && snaps ) {
1154 spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1155 }
1156 } else { // roll/unroll
1157 // arg of the spiral outer end
1158 double arg_1;
1159 sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1161 // its fractional part after the whole turns are subtracted
1162 double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1164 // arg of the mouse point relative to spiral center
1165 double mouse_angle = atan2(dy, dx);
1166 if (mouse_angle < 0)
1167 mouse_angle += 2*M_PI;
1169 // snap if ctrl
1170 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1171 mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1172 }
1174 // by how much we want to rotate the outer point
1175 double diff = mouse_angle - arg_r;
1176 if (diff > M_PI)
1177 diff -= 2*M_PI;
1178 else if (diff < -M_PI)
1179 diff += 2*M_PI;
1181 // calculate the new rad;
1182 // the value of t corresponding to the angle arg_1 + diff:
1183 double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1184 // the rad at that t:
1185 double rad_new = 0;
1186 if (t_temp > spiral->t0)
1187 sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1189 // change the revo (converting diff from radians to the number of turns)
1190 spiral->revo += diff/(2*M_PI);
1191 if (spiral->revo < 1e-3)
1192 spiral->revo = 1e-3;
1194 // if alt not pressed and the values are sane, change the rad
1195 if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1196 // adjust t0 too so that the inner point stays unmoved
1197 double r0;
1198 sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1199 spiral->rad = rad_new;
1200 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1201 }
1202 if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1203 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1204 }
1206 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1207 }
1209 NR::Point
1210 SpiralKnotHolderEntityInner::knot_get()
1211 {
1212 SPSpiral *spiral = SP_SPIRAL(item);
1214 return sp_spiral_get_xy(spiral, spiral->t0);
1215 }
1217 NR::Point
1218 SpiralKnotHolderEntityOuter::knot_get()
1219 {
1220 SPSpiral *spiral = SP_SPIRAL(item);
1222 return sp_spiral_get_xy(spiral, 1.0);
1223 }
1225 void
1226 SpiralKnotHolderEntityInner::knot_click(guint state)
1227 {
1228 SPSpiral *spiral = SP_SPIRAL(item);
1230 if (state & GDK_MOD1_MASK) {
1231 spiral->exp = 1;
1232 ((SPObject *)spiral)->updateRepr();
1233 } else if (state & GDK_SHIFT_MASK) {
1234 spiral->t0 = 0;
1235 ((SPObject *)spiral)->updateRepr();
1236 }
1237 }
1239 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1240 KnotHolder(desktop, item, relhandler)
1241 {
1242 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1243 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1244 entity_inner->create(desktop, item, this,
1245 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1246 "with <b>Alt</b> to converge/diverge"));
1247 entity_outer->create(desktop, item, this,
1248 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1249 "with <b>Shift</b> to scale/rotate"));
1250 entity.push_back(entity_inner);
1251 entity.push_back(entity_outer);
1253 add_pattern_knotholder();
1254 }
1256 /* SPOffset */
1258 class OffsetKnotHolderEntity : public KnotHolderEntity {
1259 public:
1260 virtual NR::Point knot_get();
1261 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1262 };
1264 void
1265 OffsetKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
1266 {
1267 SPOffset *offset = SP_OFFSET(item);
1269 offset->rad = sp_offset_distance_to_original(offset, p);
1270 offset->knot = p;
1271 offset->knotSet = true;
1273 ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1274 }
1277 NR::Point
1278 OffsetKnotHolderEntity::knot_get()
1279 {
1280 SPOffset *offset = SP_OFFSET(item);
1282 NR::Point np;
1283 sp_offset_top_point(offset,&np);
1284 return np;
1285 }
1287 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1288 KnotHolder(desktop, item, relhandler)
1289 {
1290 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1291 entity_offset->create(desktop, item, this,
1292 _("Adjust the <b>offset distance</b>"));
1293 entity.push_back(entity_offset);
1295 add_pattern_knotholder();
1296 }
1298 class FlowtextKnotHolderEntity : public KnotHolderEntity {
1299 public:
1300 virtual NR::Point knot_get();
1301 virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1302 };
1304 NR::Point
1305 FlowtextKnotHolderEntity::knot_get()
1306 {
1307 SPRect *rect = SP_RECT(item);
1309 return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1310 }
1312 void
1313 FlowtextKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
1314 {
1315 SPRect *rect = SP_RECT(item);
1317 sp_rect_wh_set_internal(rect, p, origin, state);
1318 }
1320 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1321 KnotHolder(desktop, item, relhandler)
1322 {
1323 g_assert(item != NULL);
1325 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1326 entity_flowtext->create(desktop, item, this,
1327 _("Drag to resize the <b>flowed text frame</b>"));
1328 entity.push_back(entity_flowtext);
1329 }
1331 /*
1332 Local Variables:
1333 mode:c++
1334 c-file-style:"stroustrup"
1335 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1336 indent-tabs-mode:nil
1337 fill-column:99
1338 End:
1339 */
1340 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :