Code

Formerly static function used for snapping is now a private member of KnotHolderEntity
[inkscape.git] / src / object-edit.cpp
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 "desktop-affine.h"
30 #include "style.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "sp-namedview.h"
34 #include "live_effects/effect.h"
36 #include "sp-pattern.h"
37 #include "sp-path.h"
39 #include <glibmm/i18n.h>
41 #include "object-edit.h"
43 #include <libnr/nr-scale-ops.h>
44 #include <libnr/nr-matrix-div.h>
46 #include "xml/repr.h"
48 #include "2geom/isnan.h"
50 #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
52 static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
53 {
54     KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL);
56     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
57     effect->addHandles(knot_holder, desktop, item);
59     return knot_holder;
60 }
62 KnotHolder *
63 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
64 {
65     KnotHolder *knotholder = NULL;
67     if (SP_IS_LPE_ITEM(item) &&
68         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
69         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
70         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
71         knotholder = sp_lpe_knot_holder(item, desktop);
72     } else if (SP_IS_RECT(item)) {
73         knotholder = new RectKnotHolder(desktop, item, NULL);
74     } else if (SP_IS_BOX3D(item)) {
75         knotholder = new Box3DKnotHolder(desktop, item, NULL);
76     } else if (SP_IS_ARC(item)) {
77         knotholder = new ArcKnotHolder(desktop, item, NULL);
78     } else if (SP_IS_STAR(item)) {
79         knotholder = new StarKnotHolder(desktop, item, NULL);
80     } else if (SP_IS_SPIRAL(item)) {
81         knotholder = new SpiralKnotHolder(desktop, item, NULL);
82     } else if (SP_IS_OFFSET(item)) {
83         knotholder = new OffsetKnotHolder(desktop, item, NULL);
84     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
85         knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
86     } else if ((SP_OBJECT(item)->style->fill.isPaintserver())
87                && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style))) {
88         knotholder = new KnotHolder(desktop, item, NULL);
89         knotholder->add_pattern_knotholder();
90     }
92     return knotholder;
93 }
95 /* SPRect */
97 /* handle for horizontal rounding radius */
98 class RectKnotHolderEntityRX : public KnotHolderEntity {
99 public:
100     virtual NR::Point knot_get();
101     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
102     virtual void knot_click(guint state);
103 };
105 /* handle for vertical rounding radius */
106 class RectKnotHolderEntityRY : public KnotHolderEntity {
107 public:
108     virtual NR::Point knot_get();
109     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
110     virtual void knot_click(guint state);
111 };
113 /* handle for width/height adjustment */
114 class RectKnotHolderEntityWH : public KnotHolderEntity {
115 public:
116     virtual NR::Point knot_get();
117     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
119 protected:
120     void set_internal(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 NR::Point
131 RectKnotHolderEntityRX::knot_get()
133     SPRect *rect = SP_RECT(item);
135     return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
138 void
139 RectKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
141     SPRect *rect = SP_RECT(item);
143     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
144     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
145     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
147     if (state & GDK_CONTROL_MASK) {
148         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
149         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
150         rect->rx._set = rect->ry._set = true;
152     } else {
153         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
154         rect->rx._set = true;
155     }
157     update_knot();
159     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
162 void
163 RectKnotHolderEntityRX::knot_click(guint state)
165     SPRect *rect = SP_RECT(item);
167     if (state & GDK_SHIFT_MASK) {
168         /* remove rounding from rectangle */
169         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
170         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
171     } else if (state & GDK_CONTROL_MASK) {
172         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
173         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
174     }
176     update_knot();
179 NR::Point
180 RectKnotHolderEntityRY::knot_get()
182     SPRect *rect = SP_RECT(item);
184     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
187 void
188 RectKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
190     SPRect *rect = SP_RECT(item);
192     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
193     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
194     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
196     if (state & GDK_CONTROL_MASK) {
197         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
198         rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
199         rect->ry._set = rect->rx._set = true;
200     } else {
201         if (!rect->rx._set || rect->rx.computed == 0) {
202             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
203                                       0.0,
204                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
205         } else {
206             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
207                                       0.0,
208                                       rect->height.computed / 2.0);
209         }
211         rect->ry._set = true;
212     }
214     update_knot();
216     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
219 void
220 RectKnotHolderEntityRY::knot_click(guint state)
222     SPRect *rect = SP_RECT(item);
224     if (state & GDK_SHIFT_MASK) {
225         /* remove rounding */
226         SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
227         SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
228     } else if (state & GDK_CONTROL_MASK) {
229         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
230         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
231     }
234 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
236 static void sp_rect_clamp_radii(SPRect *rect)
238     // clamp rounding radii so that they do not exceed width/height
239     if (2 * rect->rx.computed > rect->width.computed) {
240         rect->rx.computed = 0.5 * rect->width.computed;
241         rect->rx._set = true;
242     }
243     if (2 * rect->ry.computed > rect->height.computed) {
244         rect->ry.computed = 0.5 * rect->height.computed;
245         rect->ry._set = true;
246     }
249 NR::Point
250 RectKnotHolderEntityWH::knot_get()
252     SPRect *rect = SP_RECT(item);
254     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
257 void
258 RectKnotHolderEntityWH::set_internal(NR::Point const &p, NR::Point const &origin, guint state)
260     SPRect *rect = SP_RECT(item);
261     NR::Point const s = snap_knot_position(p);
263     if (state & GDK_CONTROL_MASK) {
264         // original width/height when drag started
265         gdouble const w_orig = (origin[NR::X] - rect->x.computed);
266         gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
268         //original ratio
269         gdouble const ratio = (w_orig / h_orig);
271         // mouse displacement since drag started
272         gdouble const minx = s[NR::X] - origin[NR::X];
273         gdouble const miny = s[NR::Y] - origin[NR::Y];
275         if (fabs(minx) > fabs(miny)) {
277             // snap to horizontal or diagonal
278             rect->width.computed = MAX(w_orig + minx, 0);
279             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
280                 // closer to the diagonal and in same-sign quarters, change both using ratio
281                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
282             } else {
283                 // closer to the horizontal, change only width, height is h_orig
284                 rect->height.computed = MAX(h_orig, 0);
285             }
287         } else {
288             // snap to vertical or diagonal
289             rect->height.computed = MAX(h_orig + miny, 0);
290             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
291                 // closer to the diagonal and in same-sign quarters, change both using ratio
292                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
293             } else {
294                 // closer to the vertical, change only height, width is w_orig
295                 rect->width.computed = MAX(w_orig, 0);
296             }
297         }
299         rect->width._set = rect->height._set = true;
301     } else {
302         // move freely
303         rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
304         rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
305         rect->width._set = rect->height._set = true;
306     }
308     sp_rect_clamp_radii(rect);
310     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
313 void
314 RectKnotHolderEntityWH::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
316     set_internal(p, origin, state);
317     update_knot();
320 NR::Point
321 RectKnotHolderEntityXY::knot_get()
323     SPRect *rect = SP_RECT(item);
325     return NR::Point(rect->x.computed, rect->y.computed);
328 void
329 RectKnotHolderEntityXY::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
331     SPRect *rect = SP_RECT(item);
333     // opposite corner (unmoved)
334     gdouble opposite_x = (rect->x.computed + rect->width.computed);
335     gdouble opposite_y = (rect->y.computed + rect->height.computed);
337     // original width/height when drag started
338     gdouble w_orig = opposite_x - origin[NR::X];
339     gdouble h_orig = opposite_y - origin[NR::Y];
341     NR::Point const s = snap_knot_position(p);
343     // mouse displacement since drag started
344     gdouble minx = s[NR::X] - origin[NR::X];
345     gdouble miny = s[NR::Y] - origin[NR::Y];
347     if (state & GDK_CONTROL_MASK) {
348         //original ratio
349         gdouble ratio = (w_orig / h_orig);
351         if (fabs(minx) > fabs(miny)) {
353             // snap to horizontal or diagonal
354             rect->x.computed = MIN(s[NR::X], opposite_x);
355             rect->width.computed = MAX(w_orig - minx, 0);
356             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
357                 // closer to the diagonal and in same-sign quarters, change both using ratio
358                 rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
359                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
360             } else {
361                 // closer to the horizontal, change only width, height is h_orig
362                 rect->y.computed = MIN(origin[NR::Y], opposite_y);
363                 rect->height.computed = MAX(h_orig, 0);
364             }
366         } else {
368             // snap to vertical or diagonal
369             rect->y.computed = MIN(s[NR::Y], opposite_y);
370             rect->height.computed = MAX(h_orig - miny, 0);
371             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
372                 // closer to the diagonal and in same-sign quarters, change both using ratio
373                 rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
374                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
375             } else {
376                 // closer to the vertical, change only height, width is w_orig
377                 rect->x.computed = MIN(origin[NR::X], opposite_x);
378                 rect->width.computed = MAX(w_orig, 0);
379             }
381         }
383         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
385     } else {
386         // move freely
387         rect->x.computed = MIN(s[NR::X], opposite_x);
388         rect->width.computed = MAX(w_orig - minx, 0);
389         rect->y.computed = MIN(s[NR::Y], opposite_y);
390         rect->height.computed = MAX(h_orig - miny, 0);
391         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
392     }
394     sp_rect_clamp_radii(rect);
396     update_knot();
398     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
401 RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
402     KnotHolder(desktop, item, relhandler)
404     RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
405     RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
406     RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
407     RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
408     entity_rx->create(desktop, item, this,
409                       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
410                         "to make the vertical radius the same"),
411                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
412     entity_ry->create(desktop, item, this,
413                       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
414                         "to make the horizontal radius the same"),
415                       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
416     entity_wh->create(desktop, item, this,
417                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
418                         "to lock ratio or stretch in one dimension only"),
419                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
420     entity_xy->create(desktop, item, this,
421                       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b>"
422                         "to lock ratio or stretch in one dimension only"),
423                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
424     entity.push_back(entity_rx);
425     entity.push_back(entity_ry);
426     entity.push_back(entity_wh);
427     entity.push_back(entity_xy);
429     add_pattern_knotholder();
432 /* Box3D (= the new 3D box structure) */
434 class Box3DKnotHolderEntity : public KnotHolderEntity {
435 public:
436     virtual NR::Point knot_get() = 0;
437     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state) = 0;
439     NR::Point knot_get_generic(SPItem *item, unsigned int knot_id);
440     void knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &p, guint state);
441 };
443 NR::Point
444 Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id)
446     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
449 void
450 Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, NR::Point const &new_pos, guint state)
452     NR::Point const s = snap_knot_position(new_pos);
454     g_assert(item != NULL);
455     SPBox3D *box = SP_BOX3D(item);
456     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
458     Box3D::Axis movement;
459     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
460         movement = Box3D::XY;
461     } else {
462         movement = Box3D::Z;
463     }
465     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
466     box3d_set_z_orders(box);
467     box3d_position_set(box);
470 class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
471 public:
472     virtual NR::Point knot_get();
473     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
474 };
476 class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
477 public:
478     virtual NR::Point knot_get();
479     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
480 };
482 class Box3DKnotHolderEntity2 : 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 Box3DKnotHolderEntity3 : 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 Box3DKnotHolderEntity4 : 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 Box3DKnotHolderEntity5 : 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 Box3DKnotHolderEntity6 : 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 Box3DKnotHolderEntity7 : 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 Box3DKnotHolderEntityCenter : public KnotHolderEntity {
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 NR::Point
525 Box3DKnotHolderEntity0::knot_get()
527     return knot_get_generic(item, 0);
530 NR::Point
531 Box3DKnotHolderEntity1::knot_get()
533     return knot_get_generic(item, 1);
536 NR::Point
537 Box3DKnotHolderEntity2::knot_get()
539     return knot_get_generic(item, 2);
542 NR::Point
543 Box3DKnotHolderEntity3::knot_get()
545     return knot_get_generic(item, 3);
548 NR::Point
549 Box3DKnotHolderEntity4::knot_get()
551     return knot_get_generic(item, 4);
554 NR::Point
555 Box3DKnotHolderEntity5::knot_get()
557     return knot_get_generic(item, 5);
560 NR::Point
561 Box3DKnotHolderEntity6::knot_get()
563     return knot_get_generic(item, 6);
566 NR::Point
567 Box3DKnotHolderEntity7::knot_get()
569     return knot_get_generic(item, 7);
572 NR::Point
573 Box3DKnotHolderEntityCenter::knot_get()
575     return box3d_get_center_screen(SP_BOX3D(item));
578 void
579 Box3DKnotHolderEntity0::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
581     knot_set_generic(item, 0, new_pos, state);
584 void
585 Box3DKnotHolderEntity1::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
587     knot_set_generic(item, 1, new_pos, state);
590 void
591 Box3DKnotHolderEntity2::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
593     knot_set_generic(item, 2, new_pos, state);
596 void
597 Box3DKnotHolderEntity3::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
599     knot_set_generic(item, 3, new_pos, state);
602 void
603 Box3DKnotHolderEntity4::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
605     knot_set_generic(item, 4, new_pos, state);
608 void
609 Box3DKnotHolderEntity5::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
611     knot_set_generic(item, 5, new_pos, state);
614 void
615 Box3DKnotHolderEntity6::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
617     knot_set_generic(item, 6, new_pos, state);
620 void
621 Box3DKnotHolderEntity7::knot_set(NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
623     knot_set_generic(item, 7, new_pos, state);
626 void
627 Box3DKnotHolderEntityCenter::knot_set(NR::Point const &new_pos, NR::Point const &origin, guint state)
629     NR::Point const s = snap_knot_position(new_pos);
631     SPBox3D *box = SP_BOX3D(item);
632     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
634     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
635                       state & GDK_CONTROL_MASK);
637     box3d_set_z_orders(box);
638     box3d_position_set(box);
641 Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
642     KnotHolder(desktop, item, relhandler)
644     Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
645     Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
646     Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
647     Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
648     Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
649     Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
650     Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
651     Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
652     Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
654     entity_corner0->create(desktop, item, this,
655                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
656                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
657     entity_corner1->create(desktop, item, this,
658                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
659                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
660     entity_corner2->create(desktop, item, this,
661                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
662                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
663     entity_corner3->create(desktop, item, this,
664                            _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
665                              "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
666     entity_corner4->create(desktop, item, this,
667                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
668                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
669     entity_corner5->create(desktop, item, this,
670                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
671                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
672     entity_corner6->create(desktop, item, this,
673                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
674                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
675     entity_corner7->create(desktop, item, this,
676                      _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
677                        "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
678     entity_center->create(desktop, item, this,
679                           _("Move the box in perspective"),
680                           SP_KNOT_SHAPE_CROSS);
682     entity.push_back(entity_corner0);
683     entity.push_back(entity_corner1);
684     entity.push_back(entity_corner2);
685     entity.push_back(entity_corner3);
686     entity.push_back(entity_corner4);
687     entity.push_back(entity_corner5);
688     entity.push_back(entity_corner6);
689     entity.push_back(entity_corner7);
690     entity.push_back(entity_center);
692     add_pattern_knotholder();
695 /* SPArc */
697 class ArcKnotHolderEntityStart : public KnotHolderEntity {
698 public:
699     virtual NR::Point knot_get();
700     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
701 };
703 class ArcKnotHolderEntityEnd : public KnotHolderEntity {
704 public:
705     virtual NR::Point knot_get();
706     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
707     virtual void knot_click(guint state);
708 };
710 class ArcKnotHolderEntityRX : public KnotHolderEntity {
711 public:
712     virtual NR::Point knot_get();
713     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
714     virtual void knot_click(guint state);
715 };
717 class ArcKnotHolderEntityRY : public KnotHolderEntity {
718 public:
719     virtual NR::Point knot_get();
720     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
721     virtual void knot_click(guint state);
722 };
724 /*
725  * return values:
726  *   1  : inside
727  *   0  : on the curves
728  *   -1 : outside
729  */
730 static gint
731 sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
733     gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
734     gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
736     gdouble s = dx * dx + dy * dy;
737     if (s < 1.0) return 1;
738     if (s > 1.0) return -1;
739     return 0;
742 void
743 ArcKnotHolderEntityStart::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
745     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
747     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
748     SPArc *arc = SP_ARC(item);
750     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
752     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
753     NR::scale sc(ge->rx.computed, ge->ry.computed);
754     ge->start = atan2(delta * sc.inverse());
755     if ( ( state & GDK_CONTROL_MASK )
756          && snaps )
757     {
758         ge->start = sp_round(ge->start, M_PI/snaps);
759     }
760     sp_genericellipse_normalize(ge);
761     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
764 NR::Point
765 ArcKnotHolderEntityStart::knot_get()
767     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
768     SPArc *arc = SP_ARC(item);
770     return sp_arc_get_xy(arc, ge->start);
773 void
774 ArcKnotHolderEntityEnd::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
776     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
778     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
779     SPArc *arc = SP_ARC(item);
781     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
783     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
784     NR::scale sc(ge->rx.computed, ge->ry.computed);
785     ge->end = atan2(delta * sc.inverse());
786     if ( ( state & GDK_CONTROL_MASK )
787          && snaps )
788     {
789         ge->end = sp_round(ge->end, M_PI/snaps);
790     }
791     sp_genericellipse_normalize(ge);
792     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
795 NR::Point
796 ArcKnotHolderEntityEnd::knot_get()
798     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
799     SPArc *arc = SP_ARC(item);
801     return sp_arc_get_xy(arc, ge->end);
805 void
806 ArcKnotHolderEntityEnd::knot_click(guint state)
808     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
810     if (state & GDK_SHIFT_MASK) {
811         ge->end = ge->start = 0;
812         ((SPObject *)ge)->updateRepr();
813     }
817 void
818 ArcKnotHolderEntityRX::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
820     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
822     NR::Point const s = snap_knot_position(p);
824     ge->rx.computed = fabs( ge->cx.computed - s[NR::X] );
826     if ( state & GDK_CONTROL_MASK ) {
827         ge->ry.computed = ge->rx.computed;
828     }
830     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
833 NR::Point
834 ArcKnotHolderEntityRX::knot_get()
836     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
838     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(ge->rx.computed, 0));
841 void
842 ArcKnotHolderEntityRX::knot_click(guint state)
844     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
846     if (state & GDK_CONTROL_MASK) {
847         ge->ry.computed = ge->rx.computed;
848         ((SPObject *)ge)->updateRepr();
849     }
852 void
853 ArcKnotHolderEntityRY::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
855     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
857     NR::Point const s = snap_knot_position(p);
859     ge->ry.computed = fabs( ge->cy.computed - s[NR::Y] );
861     if ( state & GDK_CONTROL_MASK ) {
862         ge->rx.computed = ge->ry.computed;
863     }
865     ((SPObject *)item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
868 NR::Point
869 ArcKnotHolderEntityRY::knot_get()
871     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
873     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(0, ge->ry.computed));
876 void
877 ArcKnotHolderEntityRY::knot_click(guint state)
879     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
881     if (state & GDK_CONTROL_MASK) {
882         ge->rx.computed = ge->ry.computed;
883         ((SPObject *)ge)->updateRepr();
884     }
887 ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
888     KnotHolder(desktop, item, relhandler)
890     ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
891     ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
892     ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
893     ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
894     entity_rx->create(desktop, item, this,
895                       _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"),
896                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
897     entity_ry->create(desktop, item, this,
898                       _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"),
899                       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR);
900     entity_start->create(desktop, item, this,
901                          _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b>"
902                            "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
903                          SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
904     entity_end->create(desktop, item, this,
905                        _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; "
906                          "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"),
907                        SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR);
908     entity.push_back(entity_rx);
909     entity.push_back(entity_ry);
910     entity.push_back(entity_start);
911     entity.push_back(entity_end);
913     add_pattern_knotholder();
916 /* SPStar */
918 class StarKnotHolderEntity1 : public KnotHolderEntity {
919 public:
920     virtual NR::Point knot_get();
921     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
922     virtual void knot_click(guint state);
923 };
925 class StarKnotHolderEntity2 : public KnotHolderEntity {
926 public:
927     virtual NR::Point knot_get();
928     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
929     virtual void knot_click(guint state);
930 };
932 void
933 StarKnotHolderEntity1::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
935     SPStar *star = SP_STAR(item);
937     NR::Point const s = snap_knot_position(p);
939     NR::Point d = s - star->center;
941     double arg1 = atan2(d);
942     double darg1 = arg1 - star->arg[0];
944     if (state & GDK_MOD1_MASK) {
945         star->randomized = darg1/(star->arg[0] - star->arg[1]);
946     } else if (state & GDK_SHIFT_MASK) {
947         star->rounded = darg1/(star->arg[0] - star->arg[1]);
948     } else if (state & GDK_CONTROL_MASK) {
949         star->r[0]    = L2(d);
950     } else {
951         star->r[0]    = L2(d);
952         star->arg[0]  = arg1;
953         star->arg[1] += darg1;
954     }
955     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
958 void
959 StarKnotHolderEntity2::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
961     SPStar *star = SP_STAR(item);
963     NR::Point const s = snap_knot_position(p);
965     if (star->flatsided == false) {
966         NR::Point d = s - star->center;
968         double arg1 = atan2(d);
969         double darg1 = arg1 - star->arg[1];
971         if (state & GDK_MOD1_MASK) {
972             star->randomized = darg1/(star->arg[0] - star->arg[1]);
973         } else if (state & GDK_SHIFT_MASK) {
974             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
975         } else if (state & GDK_CONTROL_MASK) {
976             star->r[1]   = L2(d);
977             star->arg[1] = star->arg[0] + M_PI / star->sides;
978         }
979         else {
980             star->r[1]   = L2(d);
981             star->arg[1] = atan2(d);
982         }
983         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
984     }
987 NR::Point
988 StarKnotHolderEntity1::knot_get()
990     g_assert(item != NULL);
992     SPStar *star = SP_STAR(item);
994     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
998 NR::Point
999 StarKnotHolderEntity2::knot_get()
1001     g_assert(item != NULL);
1003     SPStar *star = SP_STAR(item);
1005     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1008 static void
1009 sp_star_knot_click(SPItem *item, guint state)
1011     SPStar *star = SP_STAR(item);
1013     if (state & GDK_MOD1_MASK) {
1014         star->randomized = 0;
1015         ((SPObject *)star)->updateRepr();
1016     } else if (state & GDK_SHIFT_MASK) {
1017         star->rounded = 0;
1018         ((SPObject *)star)->updateRepr();
1019     } else if (state & GDK_CONTROL_MASK) {
1020         star->arg[1] = star->arg[0] + M_PI / star->sides;
1021         ((SPObject *)star)->updateRepr();
1022     }
1025 void
1026 StarKnotHolderEntity1::knot_click(guint state)
1028     return sp_star_knot_click(item, state);
1031 void
1032 StarKnotHolderEntity2::knot_click(guint state)
1034     return sp_star_knot_click(item, state);
1037 StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1038     KnotHolder(desktop, item, relhandler)
1040     SPStar *star = SP_STAR(item);
1042     StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1043     entity1->create(desktop, item, this,
1044                     _("Adjust the <b>tip radius</b> of the star or polygon; "
1045                       "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1046     entity.push_back(entity1);
1048     if (star->flatsided == false) {
1049         StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1050         entity2->create(desktop, item, this,
1051                         _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1052                           "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1053         entity.push_back(entity2);
1054     }
1056     add_pattern_knotholder();
1059 /* SPSpiral */
1061 class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1062 public:
1063     virtual NR::Point knot_get();
1064     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1065     virtual void knot_click(guint state);
1066 };
1068 class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1069 public:
1070     virtual NR::Point knot_get();
1071     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1072 };
1075 /*
1076  * set attributes via inner (t=t0) knot point:
1077  *   [default] increase/decrease inner point
1078  *   [shift]   increase/decrease inner and outer arg synchronizely
1079  *   [control] constrain inner arg to round per PI/4
1080  */
1081 void
1082 SpiralKnotHolderEntityInner::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1084     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1086     SPSpiral *spiral = SP_SPIRAL(item);
1088     gdouble   dx = p[NR::X] - spiral->cx;
1089     gdouble   dy = p[NR::Y] - spiral->cy;
1091     if (state & GDK_MOD1_MASK) {
1092         // adjust divergence by vertical drag, relative to rad
1093         double new_exp = (spiral->rad + dy)/(spiral->rad);
1094         spiral->exp = new_exp > 0? new_exp : 0;
1095     } else {
1096         // roll/unroll from inside
1097         gdouble   arg_t0;
1098         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1100         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1101         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1102         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1104         /* round inner arg per PI/snaps, if CTRL is pressed */
1105         if ( ( state & GDK_CONTROL_MASK )
1106              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1107              && ( snaps != 0 ) ) {
1108             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1109             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1110         }
1112         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1113     }
1115     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1118 /*
1119  * set attributes via outer (t=1) knot point:
1120  *   [default] increase/decrease revolution factor
1121  *   [control] constrain inner arg to round per PI/4
1122  */
1123 void
1124 SpiralKnotHolderEntityOuter::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint state)
1126     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1128     SPSpiral *spiral = SP_SPIRAL(item);
1130     gdouble  dx = p[NR::X] - spiral->cx;
1131     gdouble  dy = p[NR::Y] - spiral->cy;
1133     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1134         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1135         if (!(state & GDK_MOD1_MASK)) {
1136             // if alt not pressed, change also rad; otherwise it is locked
1137             spiral->rad = MAX(hypot(dx, dy), 0.001);
1138         }
1139         if ( ( state & GDK_CONTROL_MASK )
1140              && snaps ) {
1141             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1142         }
1143     } else { // roll/unroll
1144         // arg of the spiral outer end
1145         double arg_1;
1146         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1148         // its fractional part after the whole turns are subtracted
1149         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1151         // arg of the mouse point relative to spiral center
1152         double mouse_angle = atan2(dy, dx);
1153         if (mouse_angle < 0)
1154             mouse_angle += 2*M_PI;
1156         // snap if ctrl
1157         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1158             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1159         }
1161         // by how much we want to rotate the outer point
1162         double diff = mouse_angle - arg_r;
1163         if (diff > M_PI)
1164             diff -= 2*M_PI;
1165         else if (diff < -M_PI)
1166             diff += 2*M_PI;
1168         // calculate the new rad;
1169         // the value of t corresponding to the angle arg_1 + diff:
1170         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1171         // the rad at that t:
1172         double rad_new = 0;
1173         if (t_temp > spiral->t0)
1174             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1176         // change the revo (converting diff from radians to the number of turns)
1177         spiral->revo += diff/(2*M_PI);
1178         if (spiral->revo < 1e-3)
1179             spiral->revo = 1e-3;
1181         // if alt not pressed and the values are sane, change the rad
1182         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1183             // adjust t0 too so that the inner point stays unmoved
1184             double r0;
1185             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1186             spiral->rad = rad_new;
1187             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1188         }
1189         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1190         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1191     }
1193     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1196 NR::Point
1197 SpiralKnotHolderEntityInner::knot_get()
1199     SPSpiral *spiral = SP_SPIRAL(item);
1201     return sp_spiral_get_xy(spiral, spiral->t0);
1204 NR::Point
1205 SpiralKnotHolderEntityOuter::knot_get()
1207     SPSpiral *spiral = SP_SPIRAL(item);
1209     return sp_spiral_get_xy(spiral, 1.0);
1212 void
1213 SpiralKnotHolderEntityInner::knot_click(guint state)
1215     SPSpiral *spiral = SP_SPIRAL(item);
1217     if (state & GDK_MOD1_MASK) {
1218         spiral->exp = 1;
1219         ((SPObject *)spiral)->updateRepr();
1220     } else if (state & GDK_SHIFT_MASK) {
1221         spiral->t0 = 0;
1222         ((SPObject *)spiral)->updateRepr();
1223     }
1226 SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1227     KnotHolder(desktop, item, relhandler)
1229     SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
1230     SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
1231     entity_inner->create(desktop, item, this,
1232                          _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
1233                            "with <b>Alt</b> to converge/diverge"));
1234     entity_outer->create(desktop, item, this,
1235                          _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
1236                            "with <b>Shift</b> to scale/rotate"));
1237     entity.push_back(entity_inner);
1238     entity.push_back(entity_outer);
1240     add_pattern_knotholder();
1243 /* SPOffset */
1245 class OffsetKnotHolderEntity : public KnotHolderEntity {
1246 public:
1247     virtual NR::Point knot_get();
1248     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1249 };
1251 void
1252 OffsetKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
1254     SPOffset *offset = SP_OFFSET(item);
1256     offset->rad = sp_offset_distance_to_original(offset, p);
1257     offset->knot = p;
1258     offset->knotSet = true;
1260     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1264 NR::Point
1265 OffsetKnotHolderEntity::knot_get()
1267     SPOffset *offset = SP_OFFSET(item);
1269     NR::Point np;
1270     sp_offset_top_point(offset,&np);
1271     return np;
1274 OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1275     KnotHolder(desktop, item, relhandler)
1277     OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
1278     entity_offset->create(desktop, item, this,
1279                           _("Adjust the <b>offset distance</b>"));
1280     entity.push_back(entity_offset);
1282     add_pattern_knotholder();
1285 // TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
1286 // set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
1287 // sense logically.
1288 class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
1289 public:
1290     virtual NR::Point knot_get();
1291     virtual void knot_set(NR::Point const &p, NR::Point const &origin, guint state);
1292 };
1294 NR::Point
1295 FlowtextKnotHolderEntity::knot_get()
1297     SPRect *rect = SP_RECT(item);
1299     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1302 void
1303 FlowtextKnotHolderEntity::knot_set(NR::Point const &p, NR::Point const &origin, guint state)
1305     set_internal(p, origin, state);
1308 FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) :
1309     KnotHolder(desktop, item, relhandler)
1311     g_assert(item != NULL);
1313     FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
1314     entity_flowtext->create(desktop, item, this,
1315                             _("Drag to resize the <b>flowed text frame</b>"));
1316     entity.push_back(entity_flowtext);
1319 /*
1320   Local Variables:
1321   mode:c++
1322   c-file-style:"stroustrup"
1323   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1324   indent-tabs-mode:nil
1325   fill-column:99
1326   End:
1327 */
1328 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :