1 #define __SP_OBJECT_EDIT_C__
3 /*
4 * Node editing extension to objects
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Mitsuru Oka
9 * Maximilian Albert <maximilian.albert@gmail.com>
10 *
11 * Licensed under GNU GPL
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
20 #include "sp-item.h"
21 #include "sp-rect.h"
22 #include "box3d.h"
23 #include "sp-ellipse.h"
24 #include "sp-star.h"
25 #include "sp-spiral.h"
26 #include "sp-offset.h"
27 #include "sp-flowtext.h"
28 #include "preferences.h"
29 #include "style.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "sp-namedview.h"
33 #include "live_effects/effect.h"
35 #include "sp-pattern.h"
36 #include "sp-path.h"
38 #include <glibmm/i18n.h>
40 #include "object-edit.h"
42 #include <libnr/nr-scale-ops.h>
44 #include "xml/repr.h"
46 #include "2geom/isnan.h"
48 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
50 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
51 {
52 KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
54 Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
55 effect->addHandles(knot_holder, desktop, item);
57 return knot_holder;
58 }
60 KnotHolder *
61 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
62 {
63 KnotHolder *knotholder = NULL;
65 if (SP_IS_LPE_ITEM(item) &&
66 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
67 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
68 sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
69 knotholder = sp_lpe_knot_holder(item, desktop);
70 } else if (SP_IS_RECT(item)) {
71 knotholder = new RectKnotHolder(desktop, item, NULL);
72 } else if (SP_IS_BOX3D(item)) {
73 knotholder = new Box3DKnotHolder(desktop, item, NULL);
74 } else if (SP_IS_ARC(item)) {
75 knotholder = new ArcKnotHolder(desktop, item, NULL);
76 } else if (SP_IS_STAR(item)) {
77 knotholder = new StarKnotHolder(desktop, item, NULL);
78 } else if (SP_IS_SPIRAL(item)) {
79 knotholder = new SpiralKnotHolder(desktop, item, NULL);
80 } else if (SP_IS_OFFSET(item)) {
81 knotholder = new OffsetKnotHolder(desktop, item, NULL);
82 } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
83 knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
84 } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
85 && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
86 knotholder = new KnotHolder(desktop, item, NULL);
87 knotholder->add_pattern_knotholder();
88 }
90 return knotholder;
91 }
93 /* SPRect */
95 /* handle for horizontal rounding radius */
96 class RectKnotHolderEntityRX : public KnotHolderEntity {
97 public:
98 virtual Geom::Point knot_get();
99 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
100 virtual void knot_click(guint state);
101 };
103 /* handle for vertical rounding radius */
104 class RectKnotHolderEntityRY : public KnotHolderEntity {
105 public:
106 virtual Geom::Point knot_get();
107 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
108 virtual void knot_click(guint state);
109 };
111 /* handle for width/height adjustment */
112 class RectKnotHolderEntityWH : public KnotHolderEntity {
113 public:
114 virtual Geom::Point knot_get();
115 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
117 protected:
118 void set_internal(Geom::Point const &p, Geom::Point const &origin, guint state);
119 };
121 /* handle for x/y adjustment */
122 class RectKnotHolderEntityXY : public KnotHolderEntity {
123 public:
124 virtual Geom::Point knot_get();
125 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
126 };
128 Geom::Point
129 RectKnotHolderEntityRX::knot_get()
130 {
131 SPRect *rect = SP_RECT(item);
133 return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
134 }
136 void
137 RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
138 {
139 SPRect *rect = SP_RECT(item);
141 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
142 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
143 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
144 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)));
146 if (state & GDK_CONTROL_MASK) {
147 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
148 rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
149 rect->rx._set = rect->ry._set = true;
151 } else {
152 rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
153 rect->rx._set = true;
154 }
156 update_knot();
158 ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159 }
161 void
162 RectKnotHolderEntityRX::knot_click(guint state)
163 {
164 SPRect *rect = SP_RECT(item);
166 if (state & GDK_SHIFT_MASK) {
167 /* remove rounding from rectangle */
168 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
169 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
170 } else if (state & GDK_CONTROL_MASK) {
171 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
172 SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
173 }
175 update_knot();
176 }
178 Geom::Point
179 RectKnotHolderEntityRY::knot_get()
180 {
181 SPRect *rect = SP_RECT(item);
183 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
184 }
186 void
187 RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
188 {
189 SPRect *rect = SP_RECT(item);
191 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
192 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
193 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
194 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::ConstraintLine(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)));
196 if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
197 // resulting in a perfect circle (and not an ellipse)
198 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
199 rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
200 rect->ry._set = rect->rx._set = true;
201 } else {
202 if (!rect->rx._set || rect->rx.computed == 0) {
203 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
204 0.0,
205 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
206 } else {
207 rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed,
208 0.0,
209 rect->height.computed / 2.0);
210 }
212 rect->ry._set = true;
213 }
215 update_knot();
217 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
218 }
220 void
221 RectKnotHolderEntityRY::knot_click(guint state)
222 {
223 SPRect *rect = SP_RECT(item);
225 if (state & GDK_SHIFT_MASK) {
226 /* remove rounding */
227 SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
228 SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
229 } else if (state & GDK_CONTROL_MASK) {
230 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
231 SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
232 }
233 }
235 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
237 static void sp_rect_clamp_radii(SPRect *rect)
238 {
239 // clamp rounding radii so that they do not exceed width/height
240 if (2 * rect->rx.computed > rect->width.computed) {
241 rect->rx.computed = 0.5 * rect->width.computed;
242 rect->rx._set = true;
243 }
244 if (2 * rect->ry.computed > rect->height.computed) {
245 rect->ry.computed = 0.5 * rect->height.computed;
246 rect->ry._set = true;
247 }
248 }
250 Geom::Point
251 RectKnotHolderEntityWH::knot_get()
252 {
253 SPRect *rect = SP_RECT(item);
255 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
256 }
258 void
259 RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, guint state)
260 {
261 SPRect *rect = SP_RECT(item);
262 Geom::Point const s = snap_knot_position(p);
264 if (state & GDK_CONTROL_MASK) {
265 // original width/height when drag started
266 gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
267 gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
269 //original ratio
270 gdouble const ratio = (w_orig / h_orig);
272 // mouse displacement since drag started
273 gdouble const minx = s[Geom::X] - origin[Geom::X];
274 gdouble const miny = s[Geom::Y] - origin[Geom::Y];
276 if (fabs(minx) > fabs(miny)) {
278 // snap to horizontal or diagonal
279 rect->width.computed = MAX(w_orig + minx, 0);
280 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
281 // closer to the diagonal and in same-sign quarters, change both using ratio
282 rect->height.computed = MAX(h_orig + minx / ratio, 0);
283 } else {
284 // closer to the horizontal, change only width, height is h_orig
285 rect->height.computed = MAX(h_orig, 0);
286 }
288 } else {
289 // snap to vertical or diagonal
290 rect->height.computed = MAX(h_orig + miny, 0);
291 if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
292 // closer to the diagonal and in same-sign quarters, change both using ratio
293 rect->width.computed = MAX(w_orig + miny * ratio, 0);
294 } else {
295 // closer to the vertical, change only height, width is w_orig
296 rect->width.computed = MAX(w_orig, 0);
297 }
298 }
300 rect->width._set = rect->height._set = true;
302 } else {
303 // move freely
304 rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0);
305 rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0);
306 rect->width._set = rect->height._set = true;
307 }
309 sp_rect_clamp_radii(rect);
311 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
312 }
314 void
315 RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
316 {
317 set_internal(p, origin, state);
318 update_knot();
319 }
321 Geom::Point
322 RectKnotHolderEntityXY::knot_get()
323 {
324 SPRect *rect = SP_RECT(item);
326 return Geom::Point(rect->x.computed, rect->y.computed);
327 }
329 void
330 RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
331 {
332 SPRect *rect = SP_RECT(item);
334 // opposite corner (unmoved)
335 gdouble opposite_x = (rect->x.computed + rect->width.computed);
336 gdouble opposite_y = (rect->y.computed + rect->height.computed);
338 // original width/height when drag started
339 gdouble w_orig = opposite_x - origin[Geom::X];
340 gdouble h_orig = opposite_y - origin[Geom::Y];
342 Geom::Point const s = snap_knot_position(p);
344 // mouse displacement since drag started
345 gdouble minx = s[Geom::X] - origin[Geom::X];
346 gdouble miny = s[Geom::Y] - origin[Geom::Y];
348 if (state & GDK_CONTROL_MASK) {
349 //original ratio
350 gdouble ratio = (w_orig / h_orig);
352 if (fabs(minx) > fabs(miny)) {
354 // snap to horizontal or diagonal
355 rect->x.computed = MIN(s[Geom::X], opposite_x);
356 rect->width.computed = MAX(w_orig - minx, 0);
357 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
358 // closer to the diagonal and in same-sign quarters, change both using ratio
359 rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
360 rect->height.computed = MAX(h_orig - minx / ratio, 0);
361 } else {
362 // closer to the horizontal, change only width, height is h_orig
363 rect->y.computed = MIN(origin[Geom::Y], opposite_y);
364 rect->height.computed = MAX(h_orig, 0);
365 }
367 } else {
369 // snap to vertical or diagonal
370 rect->y.computed = MIN(s[Geom::Y], opposite_y);
371 rect->height.computed = MAX(h_orig - miny, 0);
372 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
373 // closer to the diagonal and in same-sign quarters, change both using ratio
374 rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x);
375 rect->width.computed = MAX(w_orig - miny * ratio, 0);
376 } else {
377 // closer to the vertical, change only height, width is w_orig
378 rect->x.computed = MIN(origin[Geom::X], opposite_x);
379 rect->width.computed = MAX(w_orig, 0);
380 }
382 }
384 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
386 } else {
387 // move freely
388 rect->x.computed = MIN(s[Geom::X], opposite_x);
389 rect->width.computed = MAX(w_orig - minx, 0);
390 rect->y.computed = MIN(s[Geom::Y], opposite_y);
391 rect->height.computed = MAX(h_orig - miny, 0);
392 rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
393 }
395 sp_rect_clamp_radii(rect);
397 update_knot();
399 ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
400 }
402 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
403 KnotHolder(desktop, item, relhandler)
404 {
405 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
406 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
407 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
408 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
409 entity_rx->create(desktop, item, this,
410 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
411 "to make the vertical radius the same"),
412 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
413 entity_ry->create(desktop, item, this,
414 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
415 "to make the horizontal radius the same"),
416 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
417 entity_wh->create(desktop, item, this,
418 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
419 "to lock ratio or stretch in one dimension only"),
420 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
421 entity_xy->create(desktop, item, this,
422 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
423 "to lock ratio or stretch in one dimension only"),
424 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
425 entity.push_back(entity_rx);
426 entity.push_back(entity_ry);
427 entity.push_back(entity_wh);
428 entity.push_back(entity_xy);
430 add_pattern_knotholder();
431 }
433 /* Box3D (= the new 3D box structure) */
435 class Box3DKnotHolderEntity : public KnotHolderEntity {
436 public:
437 virtual Geom::Point knot_get() = 0;
438 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) = 0;
440 Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id);
441 void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, guint state);
442 };
444 Geom::Point
445 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
446 {
447 return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
448 }
450 void
451 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, guint state)
452 {
453 Geom::Point const s = snap_knot_position(new_pos);
455 g_assert(item != NULL);
456 SPBox3D *box = SP_BOX3D(item);
457 Geom::Matrix const i2d (sp_item_i2d_affine (item));
459 Box3D::Axis movement;
460 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
461 movement = Box3D::XY;
462 } else {
463 movement = Box3D::Z;
464 }
466 box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
467 box3d_set_z_orders(box);
468 box3d_position_set(box);
469 }
471 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
472 public:
473 virtual Geom::Point knot_get();
474 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
475 };
477 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
478 public:
479 virtual Geom::Point knot_get();
480 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
481 };
483 class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
484 public:
485 virtual Geom::Point knot_get();
486 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
487 };
489 class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
490 public:
491 virtual Geom::Point knot_get();
492 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
493 };
495 class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
496 public:
497 virtual Geom::Point knot_get();
498 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
499 };
501 class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
502 public:
503 virtual Geom::Point knot_get();
504 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
505 };
507 class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
508 public:
509 virtual Geom::Point knot_get();
510 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
511 };
513 class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
514 public:
515 virtual Geom::Point knot_get();
516 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
517 };
519 class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
520 public:
521 virtual Geom::Point knot_get();
522 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
523 };
525 Geom::Point
526 Box3DKnotHolderEntity0::knot_get()
527 {
528 return knot_get_generic(item, 0);
529 }
531 Geom::Point
532 Box3DKnotHolderEntity1::knot_get()
533 {
534 return knot_get_generic(item, 1);
535 }
537 Geom::Point
538 Box3DKnotHolderEntity2::knot_get()
539 {
540 return knot_get_generic(item, 2);
541 }
543 Geom::Point
544 Box3DKnotHolderEntity3::knot_get()
545 {
546 return knot_get_generic(item, 3);
547 }
549 Geom::Point
550 Box3DKnotHolderEntity4::knot_get()
551 {
552 return knot_get_generic(item, 4);
553 }
555 Geom::Point
556 Box3DKnotHolderEntity5::knot_get()
557 {
558 return knot_get_generic(item, 5);
559 }
561 Geom::Point
562 Box3DKnotHolderEntity6::knot_get()
563 {
564 return knot_get_generic(item, 6);
565 }
567 Geom::Point
568 Box3DKnotHolderEntity7::knot_get()
569 {
570 return knot_get_generic(item, 7);
571 }
573 Geom::Point
574 Box3DKnotHolderEntityCenter::knot_get()
575 {
576 return box3d_get_center_screen(SP_BOX3D(item));
577 }
579 void
580 Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
581 {
582 knot_set_generic(item, 0, new_pos, state);
583 }
585 void
586 Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
587 {
588 knot_set_generic(item, 1, new_pos, state);
589 }
591 void
592 Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
593 {
594 knot_set_generic(item, 2, new_pos, state);
595 }
597 void
598 Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
599 {
600 knot_set_generic(item, 3, new_pos, state);
601 }
603 void
604 Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
605 {
606 knot_set_generic(item, 4, new_pos, state);
607 }
609 void
610 Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
611 {
612 knot_set_generic(item, 5, new_pos, state);
613 }
615 void
616 Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
617 {
618 knot_set_generic(item, 6, new_pos, state);
619 }
621 void
622 Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, guint state)
623 {
624 knot_set_generic(item, 7, new_pos, state);
625 }
627 void
628 Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, guint state)
629 {
630 Geom::Point const s = snap_knot_position(new_pos);
632 SPBox3D *box = SP_BOX3D(item);
633 Geom::Matrix const i2d (sp_item_i2d_affine (item));
635 box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
636 state & GDK_CONTROL_MASK);
638 box3d_set_z_orders(box);
639 box3d_position_set(box);
640 }
642 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
643 KnotHolder(desktop, item, relhandler)
644 {
645 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
646 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
647 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
648 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
649 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
650 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
651 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
652 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
653 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
655 entity_corner0->create(desktop, item, this,
656 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
657 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
658 entity_corner1->create(desktop, item, this,
659 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
660 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
661 entity_corner2->create(desktop, item, this,
662 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
663 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
664 entity_corner3->create(desktop, item, this,
665 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
666 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
667 entity_corner4->create(desktop, item, this,
668 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
669 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
670 entity_corner5->create(desktop, item, this,
671 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
672 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
673 entity_corner6->create(desktop, item, this,
674 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
675 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
676 entity_corner7->create(desktop, item, this,
677 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
678 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
679 entity_center->create(desktop, item, this,
680 _("Move the box in perspective"),
681 SP_KNOT_SHAPE_CROSS);
683 entity.push_back(entity_corner0);
684 entity.push_back(entity_corner1);
685 entity.push_back(entity_corner2);
686 entity.push_back(entity_corner3);
687 entity.push_back(entity_corner4);
688 entity.push_back(entity_corner5);
689 entity.push_back(entity_corner6);
690 entity.push_back(entity_corner7);
691 entity.push_back(entity_center);
693 add_pattern_knotholder();
694 }
696 /* SPArc */
698 class ArcKnotHolderEntityStart : public KnotHolderEntity {
699 public:
700 virtual Geom::Point knot_get();
701 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
702 };
704 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
705 public:
706 virtual Geom::Point knot_get();
707 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
708 virtual void knot_click(guint state);
709 };
711 class ArcKnotHolderEntityRX : public KnotHolderEntity {
712 public:
713 virtual Geom::Point knot_get();
714 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
715 virtual void knot_click(guint state);
716 };
718 class ArcKnotHolderEntityRY : public KnotHolderEntity {
719 public:
720 virtual Geom::Point knot_get();
721 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
722 virtual void knot_click(guint state);
723 };
725 /*
726 * return values:
727 * 1 : inside
728 * 0 : on the curves
729 * -1 : outside
730 */
731 static gint
732 sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
733 {
734 gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
735 gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
737 gdouble s = dx * dx + dy * dy;
738 if (s < 1.0) return 1;
739 if (s > 1.0) return -1;
740 return 0;
741 }
743 void
744 ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
745 {
746 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
747 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
749 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
750 SPArc *arc = SP_ARC(item);
752 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
754 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
755 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
756 ge->start = atan2(delta * sc.inverse());
757 if ( ( state & GDK_CONTROL_MASK )
758 && snaps )
759 {
760 ge->start = sp_round(ge->start, M_PI/snaps);
761 }
762 sp_genericellipse_normalize(ge);
763 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
764 }
766 Geom::Point
767 ArcKnotHolderEntityStart::knot_get()
768 {
769 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
770 SPArc *arc = SP_ARC(item);
772 return sp_arc_get_xy(arc, ge->start);
773 }
775 void
776 ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
777 {
778 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
779 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
781 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
782 SPArc *arc = SP_ARC(item);
784 ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
786 Geom::Point delta = p - Geom::Point(ge->cx.computed, ge->cy.computed);
787 Geom::Scale sc(ge->rx.computed, ge->ry.computed);
788 ge->end = atan2(delta * sc.inverse());
789 if ( ( state & GDK_CONTROL_MASK )
790 && snaps )
791 {
792 ge->end = sp_round(ge->end, M_PI/snaps);
793 }
794 sp_genericellipse_normalize(ge);
795 ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
796 }
798 Geom::Point
799 ArcKnotHolderEntityEnd::knot_get()
800 {
801 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
802 SPArc *arc = SP_ARC(item);
804 return sp_arc_get_xy(arc, ge->end);
805 }
808 void
809 ArcKnotHolderEntityEnd::knot_click(guint state)
810 {
811 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
813 if (state & GDK_SHIFT_MASK) {
814 ge->end = ge->start = 0;
815 ((SPObject *)ge)->updateRepr();
816 }
817 }
820 void
821 ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
822 {
823 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
825 Geom::Point const s = snap_knot_position(p);
827 ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] );
829 if ( state & GDK_CONTROL_MASK ) {
830 ge->ry.computed = ge->rx.computed;
831 }
833 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
834 }
836 Geom::Point
837 ArcKnotHolderEntityRX::knot_get()
838 {
839 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
841 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0));
842 }
844 void
845 ArcKnotHolderEntityRX::knot_click(guint state)
846 {
847 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
849 if (state & GDK_CONTROL_MASK) {
850 ge->ry.computed = ge->rx.computed;
851 ((SPObject *)ge)->updateRepr();
852 }
853 }
855 void
856 ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
857 {
858 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
860 Geom::Point const s = snap_knot_position(p);
862 ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] );
864 if ( state & GDK_CONTROL_MASK ) {
865 ge->rx.computed = ge->ry.computed;
866 }
868 ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
869 }
871 Geom::Point
872 ArcKnotHolderEntityRY::knot_get()
873 {
874 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
876 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed));
877 }
879 void
880 ArcKnotHolderEntityRY::knot_click(guint state)
881 {
882 SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
884 if (state & GDK_CONTROL_MASK) {
885 ge->rx.computed = ge->ry.computed;
886 ((SPObject *)ge)->updateRepr();
887 }
888 }
890 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
891 KnotHolder(desktop, item, relhandler)
892 {
893 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
894 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
895 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
896 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
897 entity_rx->create(desktop, item, this,
898 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
899 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
900 entity_ry->create(desktop, item, this,
901 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
902 SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
903 entity_start->create(desktop, item, this,
904 _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
905 "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
906 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
907 entity_end->create(desktop, item, this,
908 _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
909 "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
910 SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
911 entity.push_back(entity_rx);
912 entity.push_back(entity_ry);
913 entity.push_back(entity_start);
914 entity.push_back(entity_end);
916 add_pattern_knotholder();
917 }
919 /* SPStar */
921 class StarKnotHolderEntity1 : public KnotHolderEntity {
922 public:
923 virtual Geom::Point knot_get();
924 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
925 virtual void knot_click(guint state);
926 };
928 class StarKnotHolderEntity2 : public KnotHolderEntity {
929 public:
930 virtual Geom::Point knot_get();
931 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
932 virtual void knot_click(guint state);
933 };
935 void
936 StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
937 {
938 SPStar *star = SP_STAR(item);
940 Geom::Point const s = snap_knot_position(p);
942 Geom::Point d = s - to_2geom(star->center);
944 double arg1 = atan2(d);
945 double darg1 = arg1 - star->arg[0];
947 if (state & GDK_MOD1_MASK) {
948 star->randomized = darg1/(star->arg[0] - star->arg[1]);
949 } else if (state & GDK_SHIFT_MASK) {
950 star->rounded = darg1/(star->arg[0] - star->arg[1]);
951 } else if (state & GDK_CONTROL_MASK) {
952 star->r[0] = L2(d);
953 } else {
954 star->r[0] = L2(d);
955 star->arg[0] = arg1;
956 star->arg[1] += darg1;
957 }
958 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
959 }
961 void
962 StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
963 {
964 SPStar *star = SP_STAR(item);
966 Geom::Point const s = snap_knot_position(p);
968 if (star->flatsided == false) {
969 Geom::Point d = s - to_2geom(star->center);
971 double arg1 = atan2(d);
972 double darg1 = arg1 - star->arg[1];
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 = fabs(darg1/(star->arg[0] - star->arg[1]));
978 } else if (state & GDK_CONTROL_MASK) {
979 star->r[1] = L2(d);
980 star->arg[1] = star->arg[0] + M_PI / star->sides;
981 }
982 else {
983 star->r[1] = L2(d);
984 star->arg[1] = atan2(d);
985 }
986 ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
987 }
988 }
990 Geom::Point
991 StarKnotHolderEntity1::knot_get()
992 {
993 g_assert(item != NULL);
995 SPStar *star = SP_STAR(item);
997 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
999 }
1001 Geom::Point
1002 StarKnotHolderEntity2::knot_get()
1003 {
1004 g_assert(item != NULL);
1006 SPStar *star = SP_STAR(item);
1008 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1009 }
1011 static void
1012 sp_star_knot_click(SPItem *item, guint state)
1013 {
1014 SPStar *star = SP_STAR(item);
1016 if (state & GDK_MOD1_MASK) {
1017 star->randomized = 0;
1018 ((SPObject *)star)->updateRepr();
1019 } else if (state & GDK_SHIFT_MASK) {
1020 star->rounded = 0;
1021 ((SPObject *)star)->updateRepr();
1022 } else if (state & GDK_CONTROL_MASK) {
1023 star->arg[1] = star->arg[0] + M_PI / star->sides;
1024 ((SPObject *)star)->updateRepr();
1025 }
1026 }
1028 void
1029 StarKnotHolderEntity1::knot_click(guint state)
1030 {
1031 return sp_star_knot_click(item, state);
1032 }
1034 void
1035 StarKnotHolderEntity2::knot_click(guint state)
1036 {
1037 return sp_star_knot_click(item, state);
1038 }
1040 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1041 KnotHolder(desktop, item, relhandler)
1042 {
1043 SPStar *star = SP_STAR(item);
1045 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1046 entity1->create(desktop, item, this,
1047 _("Adjust the <b>tip radius</b> of the star or polygon; "
1048 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1049 entity.push_back(entity1);
1051 if (star->flatsided == false) {
1052 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1053 entity2->create(desktop, item, this,
1054 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1055 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1056 entity.push_back(entity2);
1057 }
1059 add_pattern_knotholder();
1060 }
1062 /* SPSpiral */
1064 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1065 public:
1066 virtual Geom::Point knot_get();
1067 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1068 virtual void knot_click(guint state);
1069 };
1071 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1072 public:
1073 virtual Geom::Point knot_get();
1074 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1075 };
1078 /*
1079 * set attributes via inner (t=t0) knot point:
1080 * [default] increase/decrease inner point
1081 * [shift] increase/decrease inner and outer arg synchronizely
1082 * [control] constrain inner arg to round per PI/4
1083 */
1084 void
1085 SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1086 {
1087 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1088 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1090 SPSpiral *spiral = SP_SPIRAL(item);
1092 gdouble dx = p[Geom::X] - spiral->cx;
1093 gdouble dy = p[Geom::Y] - spiral->cy;
1095 if (state & GDK_MOD1_MASK) {
1096 // adjust divergence by vertical drag, relative to rad
1097 double new_exp = (spiral->rad + dy)/(spiral->rad);
1098 spiral->exp = new_exp > 0? new_exp : 0;
1099 } else {
1100 // roll/unroll from inside
1101 gdouble arg_t0;
1102 sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1104 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1105 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1106 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1108 /* round inner arg per PI/snaps, if CTRL is pressed */
1109 if ( ( state & GDK_CONTROL_MASK )
1110 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1111 && ( snaps != 0 ) ) {
1112 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1113 spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1114 }
1116 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1117 }
1119 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1120 }
1122 /*
1123 * set attributes via outer (t=1) knot point:
1124 * [default] increase/decrease revolution factor
1125 * [control] constrain inner arg to round per PI/4
1126 */
1127 void
1128 SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
1129 {
1130 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1131 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1133 SPSpiral *spiral = SP_SPIRAL(item);
1135 gdouble dx = p[Geom::X] - spiral->cx;
1136 gdouble dy = p[Geom::Y] - spiral->cy;
1138 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1139 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1140 if (!(state & GDK_MOD1_MASK)) {
1141 // if alt not pressed, change also rad; otherwise it is locked
1142 spiral->rad = MAX(hypot(dx, dy), 0.001);
1143 }
1144 if ( ( state & GDK_CONTROL_MASK )
1145 && snaps ) {
1146 spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1147 }
1148 } else { // roll/unroll
1149 // arg of the spiral outer end
1150 double arg_1;
1151 sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1153 // its fractional part after the whole turns are subtracted
1154 double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1156 // arg of the mouse point relative to spiral center
1157 double mouse_angle = atan2(dy, dx);
1158 if (mouse_angle < 0)
1159 mouse_angle += 2*M_PI;
1161 // snap if ctrl
1162 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1163 mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1164 }
1166 // by how much we want to rotate the outer point
1167 double diff = mouse_angle - arg_r;
1168 if (diff > M_PI)
1169 diff -= 2*M_PI;
1170 else if (diff < -M_PI)
1171 diff += 2*M_PI;
1173 // calculate the new rad;
1174 // the value of t corresponding to the angle arg_1 + diff:
1175 double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1176 // the rad at that t:
1177 double rad_new = 0;
1178 if (t_temp > spiral->t0)
1179 sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1181 // change the revo (converting diff from radians to the number of turns)
1182 spiral->revo += diff/(2*M_PI);
1183 if (spiral->revo < 1e-3)
1184 spiral->revo = 1e-3;
1186 // if alt not pressed and the values are sane, change the rad
1187 if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1188 // adjust t0 too so that the inner point stays unmoved
1189 double r0;
1190 sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1191 spiral->rad = rad_new;
1192 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1193 }
1194 if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1195 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1196 }
1198 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1199 }
1201 Geom::Point
1202 SpiralKnotHolderEntityInner::knot_get()
1203 {
1204 SPSpiral *spiral = SP_SPIRAL(item);
1206 return sp_spiral_get_xy(spiral, spiral->t0);
1207 }
1209 Geom::Point
1210 SpiralKnotHolderEntityOuter::knot_get()
1211 {
1212 SPSpiral *spiral = SP_SPIRAL(item);
1214 return sp_spiral_get_xy(spiral, 1.0);
1215 }
1217 void
1218 SpiralKnotHolderEntityInner::knot_click(guint state)
1219 {
1220 SPSpiral *spiral = SP_SPIRAL(item);
1222 if (state & GDK_MOD1_MASK) {
1223 spiral->exp = 1;
1224 ((SPObject *)spiral)->updateRepr();
1225 } else if (state & GDK_SHIFT_MASK) {
1226 spiral->t0 = 0;
1227 ((SPObject *)spiral)->updateRepr();
1228 }
1229 }
1231 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1232 KnotHolder(desktop, item, relhandler)
1233 {
1234 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1235 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1236 entity_inner->create(desktop, item, this,
1237 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1238 "with <b>Alt</b> to converge/diverge"));
1239 entity_outer->create(desktop, item, this,
1240 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1241 "with <b>Shift</b> to scale/rotate"));
1242 entity.push_back(entity_inner);
1243 entity.push_back(entity_outer);
1245 add_pattern_knotholder();
1246 }
1248 /* SPOffset */
1250 class OffsetKnotHolderEntity : public KnotHolderEntity {
1251 public:
1252 virtual Geom::Point knot_get();
1253 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1254 };
1256 void
1257 OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
1258 {
1259 SPOffset *offset = SP_OFFSET(item);
1261 offset->rad = sp_offset_distance_to_original(offset, p);
1262 offset->knot = p;
1263 offset->knotSet = true;
1265 ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1266 }
1269 Geom::Point
1270 OffsetKnotHolderEntity::knot_get()
1271 {
1272 SPOffset *offset = SP_OFFSET(item);
1274 Geom::Point np;
1275 sp_offset_top_point(offset,&np);
1276 return np;
1277 }
1279 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1280 KnotHolder(desktop, item, relhandler)
1281 {
1282 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1283 entity_offset->create(desktop, item, this,
1284 _("Adjust the <b>offset distance</b>"));
1285 entity.push_back(entity_offset);
1287 add_pattern_knotholder();
1288 }
1290 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1291 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1292 // sense logically.
1293 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1294 public:
1295 virtual Geom::Point knot_get();
1296 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
1297 };
1299 Geom::Point
1300 FlowtextKnotHolderEntity::knot_get()
1301 {
1302 SPRect *rect = SP_RECT(item);
1304 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1305 }
1307 void
1308 FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
1309 {
1310 set_internal(p, origin, state);
1311 }
1313 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1314 KnotHolder(desktop, item, relhandler)
1315 {
1316 g_assert(item != NULL);
1318 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1319 entity_flowtext->create(desktop, item, this,
1320 _("Drag to resize the <b>flowed text frame</b>"));
1321 entity.push_back(entity_flowtext);
1322 }
1324 /*
1325 Local Variables:
1326 mode:c++
1327 c-file-style:"stroustrup"
1328 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1329 indent-tabs-mode:nil
1330 fill-column:99
1331 End:
1332 */
1333 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :