Code

LPE STACKING!
[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  *
10  * Licensed under GNU GPL
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
19 #include "sp-item.h"
20 #include "sp-rect.h"
21 #include "box3d.h"
22 #include "sp-ellipse.h"
23 #include "sp-star.h"
24 #include "sp-spiral.h"
25 #include "sp-offset.h"
26 #include "sp-flowtext.h"
27 #include "prefs-utils.h"
28 #include "inkscape.h"
29 #include "snap.h"
30 #include "desktop-affine.h"
31 #include <style.h>
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "sp-namedview.h"
35 #include "live_effects/effect.h"
37 #include "sp-pattern.h"
38 #include "sp-path.h"
40 #include <glibmm/i18n.h>
42 #include "object-edit.h"
44 #include <libnr/nr-scale-ops.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 SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop);
53 static SPKnotHolder *box3d_knot_holder(SPItem *item, SPDesktop *desktop);
54 static SPKnotHolder *sp_arc_knot_holder(SPItem *item, SPDesktop *desktop);
55 static SPKnotHolder *sp_star_knot_holder(SPItem *item, SPDesktop *desktop);
56 static SPKnotHolder *sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop);
57 static SPKnotHolder *sp_offset_knot_holder(SPItem *item, SPDesktop *desktop);
58 static SPKnotHolder *sp_misc_knot_holder(SPItem *item, SPDesktop *desktop);
59 static SPKnotHolder *sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop);
60 static void sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder);
62 static SPKnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop)
63 {
64     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
66     Inkscape::LivePathEffect::Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
67     if (!effect) {
68         g_error("sp_lpe_knot_holder: logical error, this method cannot be called with item having an LPE");
69     } else {
70         effect->addHandles(knot_holder);
71     }
73     return knot_holder;
74 }
76 SPKnotHolder *
77 sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
78 {
79     if (sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item)) &&
80         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->isVisible() &&
81         sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item))->providesKnotholder()) {
82         return sp_lpe_knot_holder(item, desktop);
83     } else if (SP_IS_RECT(item)) {
84         return sp_rect_knot_holder(item, desktop);
85     } else if (SP_IS_BOX3D(item)) {
86         return box3d_knot_holder(item, desktop);
87     } else if (SP_IS_ARC(item)) {
88         return sp_arc_knot_holder(item, desktop);
89     } else if (SP_IS_STAR(item)) {
90         return sp_star_knot_holder(item, desktop);
91     } else if (SP_IS_SPIRAL(item)) {
92         return sp_spiral_knot_holder(item, desktop);
93     } else if (SP_IS_OFFSET(item)) {
94         return sp_offset_knot_holder(item, desktop);
95     } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
96         return sp_flowtext_knot_holder(item, desktop);
97     } else {
98         return sp_misc_knot_holder(item, desktop);
99     }
101     return NULL;
105 /* Pattern manipulation */
107 static gdouble sp_pattern_extract_theta(SPPattern *pat, gdouble scale)
109     gdouble theta = asin(pat->patternTransform[1] / scale);
110     if (pat->patternTransform[0] < 0) theta = M_PI - theta ;
111     return theta;
114 static gdouble sp_pattern_extract_scale(SPPattern *pat)
116     gdouble s = pat->patternTransform[1];
117     gdouble c = pat->patternTransform[0];
118     gdouble xscale = sqrt(c * c + s * s);
119     return xscale;
122 static NR::Point sp_pattern_extract_trans(SPPattern const *pat)
124     return NR::Point(pat->patternTransform[4], pat->patternTransform[5]);
127 static void
128 sp_pattern_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
130     SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
132     NR::Point p_snapped = p;
134     if ( state & GDK_CONTROL_MASK ) {
135         if (fabs((p - origin)[NR::X]) > fabs((p - origin)[NR::Y])) {
136             p_snapped[NR::Y] = origin[NR::Y];
137         } else {
138             p_snapped[NR::X] = origin[NR::X];
139         }
140     }
142     if (state)  {
143         NR::Point const q = p_snapped - sp_pattern_extract_trans(pat);
144         sp_item_adjust_pattern(item, NR::Matrix(NR::translate(q)));
145     }
147     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
151 static NR::Point sp_pattern_xy_get(SPItem *item)
153     SPPattern const *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
154     return sp_pattern_extract_trans(pat);
157 static NR::Point sp_pattern_angle_get(SPItem *item)
159     SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
161     gdouble x = (pattern_width(pat)*0.5);
162     gdouble y = 0;
163     NR::Point delta = NR::Point(x,y);
164     gdouble scale = sp_pattern_extract_scale(pat);
165     gdouble theta = sp_pattern_extract_theta(pat, scale);
166     delta = delta * NR::Matrix(NR::rotate(theta))*NR::Matrix(NR::scale(scale,scale));
167     delta = delta + sp_pattern_extract_trans(pat);
168     return delta;
171 static void
172 sp_pattern_angle_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
174     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
176     SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
178     // get the angle from pattern 0,0 to the cursor pos
179     NR::Point delta = p - sp_pattern_extract_trans(pat);
180     gdouble theta = atan2(delta);
182     if ( state & GDK_CONTROL_MASK ) {
183         theta = sp_round(theta, M_PI/snaps);
184     }
186     // get the scale from the current transform so we can keep it.
187     gdouble scl = sp_pattern_extract_scale(pat);
188     NR::Matrix rot =  NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl));
189     NR::Point const t = sp_pattern_extract_trans(pat);
190     rot[4] = t[NR::X];
191     rot[5] = t[NR::Y];
192     sp_item_adjust_pattern(item, rot, true);
193     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
196 static void
197 sp_pattern_scale_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
199     SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
201     // Get the scale from the position of the knotholder,
202     NR::Point d = p - sp_pattern_extract_trans(pat);
203     gdouble s = NR::L2(d);
204     gdouble pat_x = pattern_width(pat) * 0.5;
205     gdouble pat_y = pattern_height(pat) * 0.5;
206     gdouble pat_h = hypot(pat_x, pat_y);
207     gdouble scl = s / pat_h;
209     // get angle from current transform, (need get current scale first to calculate angle)
210     gdouble oldscale = sp_pattern_extract_scale(pat);
211     gdouble theta = sp_pattern_extract_theta(pat,oldscale);
213     NR::Matrix rot =  NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl));
214     NR::Point const t = sp_pattern_extract_trans(pat);
215     rot[4] = t[NR::X];
216     rot[5] = t[NR::Y];
217     sp_item_adjust_pattern(item, rot, true);
218     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
222 static NR::Point sp_pattern_scale_get(SPItem *item)
224     SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
226     gdouble x = pattern_width(pat)*0.5;
227     gdouble y = pattern_height(pat)*0.5;
228     NR::Point delta = NR::Point(x,y);
229     NR::Matrix a = pat->patternTransform;
230     a[4] = 0;
231     a[5] = 0;
232     delta = delta * a;
233     delta = delta + sp_pattern_extract_trans(pat);
234     return delta;
237 /* SPRect */
239 static NR::Point snap_knot_position(SPItem *item, NR::Point const &p)
241     SPDesktop const *desktop = inkscape_active_desktop();
242     NR::Matrix const i2d (sp_item_i2d_affine (item));
243     NR::Point s = p * i2d;
244     SnapManager &m = desktop->namedview->snap_manager;
245     m.setup(desktop, item);
246     m.freeSnapReturnByRef(Inkscape::Snapper::SNAPPOINT_NODE, s);
247     return s * i2d.inverse();
250 static NR::Point sp_rect_rx_get(SPItem *item)
252     SPRect *rect = SP_RECT(item);
254     return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
257 static void sp_rect_rx_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
259     SPRect *rect = SP_RECT(item);
261     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
262     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
263     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
265     if (state & GDK_CONTROL_MASK) {
266         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
267         rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
268         rect->rx._set = rect->ry._set = true;
270     } else {
271         rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
272         rect->rx._set = true;
273     }
275     ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
279 static NR::Point sp_rect_ry_get(SPItem *item)
281     SPRect *rect = SP_RECT(item);
283     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
286 static void sp_rect_ry_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
288     SPRect *rect = SP_RECT(item);
290     //In general we cannot just snap this radius to an arbitrary point, as we have only a single
291     //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
292     //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
294     if (state & GDK_CONTROL_MASK) {
295         gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
296         rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
297         rect->ry._set = rect->rx._set = true;
298     } else {
299         if (!rect->rx._set || rect->rx.computed == 0) {
300             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
301                                       0.0,
302                                       MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
303         } else {
304             rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
305                                       0.0,
306                                       rect->height.computed / 2.0);
307         }
309         rect->ry._set = true;
310     }
312     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
315 /**
316  *  Remove rounding from a rectangle.
317  */
318 static void rect_remove_rounding(SPRect *rect)
320     SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
321     SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
324 /**
325  *  Called when the horizontal rounding radius knot is clicked.
326  */
327 static void sp_rect_rx_knot_click(SPItem *item, guint state)
329     SPRect *rect = SP_RECT(item);
331     if (state & GDK_SHIFT_MASK) {
332         rect_remove_rounding(rect);
333     } else if (state & GDK_CONTROL_MASK) {
334         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
335         SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
336     }
339 /**
340  *  Called when the vertical rounding radius knot is clicked.
341  */
342 static void sp_rect_ry_knot_click(SPItem *item, guint state)
344     SPRect *rect = SP_RECT(item);
346     if (state & GDK_SHIFT_MASK) {
347         rect_remove_rounding(rect);
348     } else if (state & GDK_CONTROL_MASK) {
349         /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
350         SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
351     }
354 #define SGN(x) ((x)>0?1:((x)<0?-1:0))
356 static void sp_rect_clamp_radii(SPRect *rect)
358     // clamp rounding radii so that they do not exceed width/height
359     if (2 * rect->rx.computed > rect->width.computed) {
360         rect->rx.computed = 0.5 * rect->width.computed;
361         rect->rx._set = true;
362     }
363     if (2 * rect->ry.computed > rect->height.computed) {
364         rect->ry.computed = 0.5 * rect->height.computed;
365         rect->ry._set = true;
366     }
369 static NR::Point sp_rect_wh_get(SPItem *item)
371     SPRect *rect = SP_RECT(item);
373     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
376 static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
378     NR::Point const s = snap_knot_position(rect, p);
380     if (state & GDK_CONTROL_MASK) {
381         // original width/height when drag started
382         gdouble const w_orig = (origin[NR::X] - rect->x.computed);
383         gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
385         //original ratio
386         gdouble const ratio = (w_orig / h_orig);
388         // mouse displacement since drag started
389         gdouble const minx = s[NR::X] - origin[NR::X];
390         gdouble const miny = s[NR::Y] - origin[NR::Y];
392         if (fabs(minx) > fabs(miny)) {
394             // snap to horizontal or diagonal
395             rect->width.computed = MAX(w_orig + minx, 0);
396             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
397                 // closer to the diagonal and in same-sign quarters, change both using ratio
398                 rect->height.computed = MAX(h_orig + minx / ratio, 0);
399             } else {
400                 // closer to the horizontal, change only width, height is h_orig
401                 rect->height.computed = MAX(h_orig, 0);
402             }
404         } else {
405             // snap to vertical or diagonal
406             rect->height.computed = MAX(h_orig + miny, 0);
407             if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
408                 // closer to the diagonal and in same-sign quarters, change both using ratio
409                 rect->width.computed = MAX(w_orig + miny * ratio, 0);
410             } else {
411                 // closer to the vertical, change only height, width is w_orig
412                 rect->width.computed = MAX(w_orig, 0);
413             }
414         }
416         rect->width._set = rect->height._set = true;
418     } else {
419         // move freely
420         rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
421         rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
422         rect->width._set = rect->height._set = true;
423     }
425     sp_rect_clamp_radii(rect);
427     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
430 static void sp_rect_wh_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
432     SPRect *rect = SP_RECT(item);
434     sp_rect_wh_set_internal(rect, p, origin, state);
437 static NR::Point sp_rect_xy_get(SPItem *item)
439     SPRect *rect = SP_RECT(item);
441     return NR::Point(rect->x.computed, rect->y.computed);
444 static void sp_rect_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
446     SPRect *rect = SP_RECT(item);
448     // opposite corner (unmoved)
449     gdouble opposite_x = (rect->x.computed + rect->width.computed);
450     gdouble opposite_y = (rect->y.computed + rect->height.computed);
452     // original width/height when drag started
453     gdouble w_orig = opposite_x - origin[NR::X];
454     gdouble h_orig = opposite_y - origin[NR::Y];
456     NR::Point const s = snap_knot_position(rect, p);
458     // mouse displacement since drag started
459     gdouble minx = s[NR::X] - origin[NR::X];
460     gdouble miny = s[NR::Y] - origin[NR::Y];
462     if (state & GDK_CONTROL_MASK) {
463         //original ratio
464         gdouble ratio = (w_orig / h_orig);
466         if (fabs(minx) > fabs(miny)) {
468             // snap to horizontal or diagonal
469             rect->x.computed = MIN(s[NR::X], opposite_x);
470             rect->width.computed = MAX(w_orig - minx, 0);
471             if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
472                 // closer to the diagonal and in same-sign quarters, change both using ratio
473                 rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
474                 rect->height.computed = MAX(h_orig - minx / ratio, 0);
475             } else {
476                 // closer to the horizontal, change only width, height is h_orig
477                 rect->y.computed = MIN(origin[NR::Y], opposite_y);
478                 rect->height.computed = MAX(h_orig, 0);
479             }
481         } else {
483             // snap to vertical or diagonal
484             rect->y.computed = MIN(s[NR::Y], opposite_y);
485             rect->height.computed = MAX(h_orig - miny, 0);
486             if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
487                 // closer to the diagonal and in same-sign quarters, change both using ratio
488                 rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
489                 rect->width.computed = MAX(w_orig - miny * ratio, 0);
490             } else {
491                 // closer to the vertical, change only height, width is w_orig
492                 rect->x.computed = MIN(origin[NR::X], opposite_x);
493                 rect->width.computed = MAX(w_orig, 0);
494             }
496         }
498         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
500     } else {
501         // move freely
502         rect->x.computed = MIN(s[NR::X], opposite_x);
503         rect->width.computed = MAX(w_orig - minx, 0);
504         rect->y.computed = MIN(s[NR::Y], opposite_y);
505         rect->height.computed = MAX(h_orig - miny, 0);
506         rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
507     }
509     sp_rect_clamp_radii(rect);
511     ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
514 static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop)
516     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
518     sp_knot_holder_add_full(
519       knot_holder, sp_rect_rx_set, sp_rect_rx_get, sp_rect_rx_knot_click,
520       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
521       _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> to make the vertical "
522         "radius the same"));
524     sp_knot_holder_add_full(
525       knot_holder, sp_rect_ry_set, sp_rect_ry_get, sp_rect_ry_knot_click,
526       SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
527       _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> to make the horizontal "
528         "radius the same")
529       );
531     sp_knot_holder_add_full(
532       knot_holder, sp_rect_wh_set, sp_rect_wh_get, NULL,
533       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
534       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> to lock ratio "
535         "or stretch in one dimension only")
536       );
538     sp_knot_holder_add_full(
539       knot_holder, sp_rect_xy_set, sp_rect_xy_get, NULL,
540       SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
541       _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> to lock ratio "
542         "or stretch in one dimension only")
543       );
545     sp_pat_knot_holder(item, knot_holder);
546     return knot_holder;
549 /* Box3D (= the new 3D box structure) */
551 static NR::Point box3d_knot_get(SPItem *item, guint knot_id)
553     return box3d_get_corner_screen(SP_BOX3D(item), knot_id);
556 static void box3d_knot_set(SPItem *item, guint knot_id, NR::Point const &new_pos, NR::Point const &/*origin*/, guint state)
558     NR::Point const s = snap_knot_position(item, new_pos);
560     g_assert(item != NULL);
561     SPBox3D *box = SP_BOX3D(item);
562     NR::Matrix const i2d (sp_item_i2d_affine (item));
564     Box3D::Axis movement;
565     if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
566         movement = Box3D::XY;
567     } else {
568         movement = Box3D::Z;
569     }
571     box3d_set_corner (box, knot_id, s * i2d, movement, (state & GDK_CONTROL_MASK));
572     box3d_set_z_orders(box);
573     box3d_position_set(box);
576 static NR::Point box3d_knot_center_get (SPItem *item)
578     return box3d_get_center_screen (SP_BOX3D(item));
581 static void box3d_knot_center_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
583     NR::Point const s = snap_knot_position(item, new_pos);
585     SPBox3D *box = SP_BOX3D(item);
586     NR::Matrix const i2d (sp_item_i2d_affine (item));
588     box3d_set_center (SP_BOX3D(item), s * i2d, origin * i2d, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
589                       state & GDK_CONTROL_MASK);
591     box3d_set_z_orders(box);
592     box3d_position_set(box);
595 static void box3d_knot0_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
597     box3d_knot_set(item, 0, new_pos, origin, state);
600 static void box3d_knot1_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
602     box3d_knot_set(item, 1, new_pos, origin, state);
605 static void box3d_knot2_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
607     box3d_knot_set(item, 2, new_pos, origin, state);
610 static void box3d_knot3_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
612     box3d_knot_set(item, 3, new_pos, origin, state);
615 static void box3d_knot4_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
617     box3d_knot_set(item, 4, new_pos, origin, state);
620 static void box3d_knot5_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
622     box3d_knot_set(item, 5, new_pos, origin, state);
625 static void box3d_knot6_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
627     box3d_knot_set(item, 6, new_pos, origin, state);
630 static void box3d_knot7_set(SPItem *item, NR::Point const &new_pos, NR::Point const &origin, guint state)
632     box3d_knot_set(item, 7, new_pos, origin, state);
635 static NR::Point box3d_knot0_get(SPItem *item)
637     return box3d_knot_get(item, 0);
640 static NR::Point box3d_knot1_get(SPItem *item)
642     return box3d_knot_get(item, 1);
645 static NR::Point box3d_knot2_get(SPItem *item)
647     return box3d_knot_get(item, 2);
650 static NR::Point box3d_knot3_get(SPItem *item)
652     return box3d_knot_get(item, 3);
655 static NR::Point box3d_knot4_get(SPItem *item)
657     return box3d_knot_get(item, 4);
660 static NR::Point box3d_knot5_get(SPItem *item)
662     return box3d_knot_get(item, 5);
665 static NR::Point box3d_knot6_get(SPItem *item)
667     return box3d_knot_get(item, 6);
670 static NR::Point box3d_knot7_get(SPItem *item)
672     return box3d_knot_get(item, 7);
675 SPKnotHolder *
676 box3d_knot_holder(SPItem *item, SPDesktop *desktop)
678     g_assert(item != NULL);
679     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
681     sp_knot_holder_add(knot_holder, box3d_knot0_set, box3d_knot0_get, NULL,
682                        _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
683     sp_knot_holder_add(knot_holder, box3d_knot1_set, box3d_knot1_get, NULL,
684                        _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
685     sp_knot_holder_add(knot_holder, box3d_knot2_set, box3d_knot2_get, NULL,
686                        _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
687     sp_knot_holder_add(knot_holder, box3d_knot3_set, box3d_knot3_get, NULL,
688                        _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
689     sp_knot_holder_add(knot_holder, box3d_knot4_set, box3d_knot4_get, NULL,
690                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
691     sp_knot_holder_add(knot_holder, box3d_knot5_set, box3d_knot5_get, NULL,
692                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
693     sp_knot_holder_add(knot_holder, box3d_knot6_set, box3d_knot6_get, NULL,
694                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
695     sp_knot_holder_add(knot_holder, box3d_knot7_set, box3d_knot7_get, NULL,
696                        _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
698     // center dragging
699     sp_knot_holder_add_full(knot_holder, box3d_knot_center_set, box3d_knot_center_get, NULL,
700                             SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,_("Move the box in perspective"));
702     sp_pat_knot_holder(item, knot_holder);
704     return knot_holder;
709 /* SPArc */
711 /*
712  * return values:
713  *   1  : inside
714  *   0  : on the curves
715  *   -1 : outside
716  */
717 static gint
718 sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
720     gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
721     gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
723     gdouble s = dx * dx + dy * dy;
724     if (s < 1.0) return 1;
725     if (s > 1.0) return -1;
726     return 0;
729 static void
730 sp_arc_start_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
732     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
734     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
735     SPArc *arc = SP_ARC(item);
737     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
739     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
740     NR::scale sc(ge->rx.computed, ge->ry.computed);
741     ge->start = atan2(delta * sc.inverse());
742     if ( ( state & GDK_CONTROL_MASK )
743          && snaps )
744     {
745         ge->start = sp_round(ge->start, M_PI/snaps);
746     }
747     sp_genericellipse_normalize(ge);
748     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
751 static NR::Point sp_arc_start_get(SPItem *item)
753     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
754     SPArc *arc = SP_ARC(item);
756     return sp_arc_get_xy(arc, ge->start);
759 static void
760 sp_arc_end_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
762     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
764     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
765     SPArc *arc = SP_ARC(item);
767     ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
769     NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
770     NR::scale sc(ge->rx.computed, ge->ry.computed);
771     ge->end = atan2(delta * sc.inverse());
772     if ( ( state & GDK_CONTROL_MASK )
773          && snaps )
774     {
775         ge->end = sp_round(ge->end, M_PI/snaps);
776     }
777     sp_genericellipse_normalize(ge);
778     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
781 static NR::Point sp_arc_end_get(SPItem *item)
783     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
784     SPArc *arc = SP_ARC(item);
786     return sp_arc_get_xy(arc, ge->end);
789 static void
790 sp_arc_startend_click(SPItem *item, guint state)
792     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
794     if (state & GDK_SHIFT_MASK) {
795         ge->end = ge->start = 0;
796         ((SPObject *)ge)->updateRepr();
797     }
801 static void
802 sp_arc_rx_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
804     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
805     SPArc *arc = SP_ARC(item);
807     NR::Point const s = snap_knot_position(arc, p);
809     ge->rx.computed = fabs( ge->cx.computed - s[NR::X] );
811     if ( state & GDK_CONTROL_MASK ) {
812         ge->ry.computed = ge->rx.computed;
813     }
815     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
818 static NR::Point sp_arc_rx_get(SPItem *item)
820     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
822     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(ge->rx.computed, 0));
825 static void
826 sp_arc_ry_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
828     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
829     SPArc *arc = SP_ARC(item);
831     NR::Point const s = snap_knot_position(arc, p);
833     ge->ry.computed = fabs( ge->cy.computed - s[NR::Y] );
835     if ( state & GDK_CONTROL_MASK ) {
836         ge->rx.computed = ge->ry.computed;
837     }
839     ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
842 static NR::Point sp_arc_ry_get(SPItem *item)
844     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
846     return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(0, ge->ry.computed));
849 static void
850 sp_arc_rx_click(SPItem *item, guint state)
852     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
854     if (state & GDK_CONTROL_MASK) {
855         ge->ry.computed = ge->rx.computed;
856         ((SPObject *)ge)->updateRepr();
857     }
860 static void
861 sp_arc_ry_click(SPItem *item, guint state)
863     SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
865     if (state & GDK_CONTROL_MASK) {
866         ge->rx.computed = ge->ry.computed;
867         ((SPObject *)ge)->updateRepr();
868     }
871 static SPKnotHolder *
872 sp_arc_knot_holder(SPItem *item, SPDesktop *desktop)
874     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
876     sp_knot_holder_add_full(knot_holder, sp_arc_rx_set, sp_arc_rx_get, sp_arc_rx_click,
877                             SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
878                             _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"));
879     sp_knot_holder_add_full(knot_holder, sp_arc_ry_set, sp_arc_ry_get, sp_arc_ry_click,
880                             SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
881                             _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"));
882     sp_knot_holder_add_full(knot_holder, sp_arc_start_set, sp_arc_start_get, sp_arc_startend_click,
883                             SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
884                             _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"));
885     sp_knot_holder_add_full(knot_holder, sp_arc_end_set, sp_arc_end_get, sp_arc_startend_click,
886                             SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
887                             _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"));
889     sp_pat_knot_holder(item, knot_holder);
891     return knot_holder;
894 /* SPStar */
896 static void
897 sp_star_knot1_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
899     SPStar *star = SP_STAR(item);
901     NR::Point const s = snap_knot_position(star, p);
903     NR::Point d = s - star->center;
905     double arg1 = atan2(d);
906     double darg1 = arg1 - star->arg[0];
908     if (state & GDK_MOD1_MASK) {
909         star->randomized = darg1/(star->arg[0] - star->arg[1]);
910     } else if (state & GDK_SHIFT_MASK) {
911         star->rounded = darg1/(star->arg[0] - star->arg[1]);
912     } else if (state & GDK_CONTROL_MASK) {
913         star->r[0]    = L2(d);
914     } else {
915         star->r[0]    = L2(d);
916         star->arg[0]  = arg1;
917         star->arg[1] += darg1;
918     }
919     ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
922 static void
923 sp_star_knot2_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
925     SPStar *star = SP_STAR(item);
927     NR::Point const s = snap_knot_position(star, p);
929     if (star->flatsided == false) {
930         NR::Point d = s - star->center;
932         double arg1 = atan2(d);
933         double darg1 = arg1 - star->arg[1];
935         if (state & GDK_MOD1_MASK) {
936             star->randomized = darg1/(star->arg[0] - star->arg[1]);
937         } else if (state & GDK_SHIFT_MASK) {
938             star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
939         } else if (state & GDK_CONTROL_MASK) {
940             star->r[1]   = L2(d);
941             star->arg[1] = star->arg[0] + M_PI / star->sides;
942         }
943         else {
944             star->r[1]   = L2(d);
945             star->arg[1] = atan2(d);
946         }
947         ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
948     }
951 static NR::Point sp_star_knot1_get(SPItem *item)
953     g_assert(item != NULL);
955     SPStar *star = SP_STAR(item);
957     return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
961 static NR::Point sp_star_knot2_get(SPItem *item)
963     g_assert(item != NULL);
965     SPStar *star = SP_STAR(item);
967     return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
970 static void
971 sp_star_knot_click(SPItem *item, guint state)
973     SPStar *star = SP_STAR(item);
975     if (state & GDK_MOD1_MASK) {
976         star->randomized = 0;
977         ((SPObject *)star)->updateRepr();
978     } else if (state & GDK_SHIFT_MASK) {
979         star->rounded = 0;
980         ((SPObject *)star)->updateRepr();
981     } else if (state & GDK_CONTROL_MASK) {
982         star->arg[1] = star->arg[0] + M_PI / star->sides;
983         ((SPObject *)star)->updateRepr();
984     }
987 static SPKnotHolder *
988 sp_star_knot_holder(SPItem *item, SPDesktop *desktop)
990     /* we don't need to get parent knot_holder */
991     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
992     g_assert(item != NULL);
994     SPStar *star = SP_STAR(item);
996     sp_knot_holder_add(knot_holder, sp_star_knot1_set, sp_star_knot1_get, sp_star_knot_click,
997                        _("Adjust the <b>tip radius</b> of the star or polygon; with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
998     if (star->flatsided == false)
999         sp_knot_holder_add(knot_holder, sp_star_knot2_set, sp_star_knot2_get, sp_star_knot_click,
1000                            _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1002     sp_pat_knot_holder(item, knot_holder);
1004     return knot_holder;
1007 /* SPSpiral */
1009 /*
1010  * set attributes via inner (t=t0) knot point:
1011  *   [default] increase/decrease inner point
1012  *   [shift]   increase/decrease inner and outer arg synchronizely
1013  *   [control] constrain inner arg to round per PI/4
1014  */
1015 static void
1016 sp_spiral_inner_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
1018     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1020     SPSpiral *spiral = SP_SPIRAL(item);
1022     gdouble   dx = p[NR::X] - spiral->cx;
1023     gdouble   dy = p[NR::Y] - spiral->cy;
1025     if (state & GDK_MOD1_MASK) {
1026         // adjust divergence by vertical drag, relative to rad
1027         double new_exp = (spiral->rad + dy)/(spiral->rad);
1028         spiral->exp = new_exp > 0? new_exp : 0;
1029     } else {
1030         // roll/unroll from inside
1031         gdouble   arg_t0;
1032         sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
1034         gdouble   arg_tmp = atan2(dy, dx) - arg_t0;
1035         gdouble   arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1036         spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1038         /* round inner arg per PI/snaps, if CTRL is pressed */
1039         if ( ( state & GDK_CONTROL_MASK )
1040              && ( fabs(spiral->revo) > SP_EPSILON_2 )
1041              && ( snaps != 0 ) ) {
1042             gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1043             spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
1044         }
1046         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1047     }
1049     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1052 /*
1053  * set attributes via outer (t=1) knot point:
1054  *   [default] increase/decrease revolution factor
1055  *   [control] constrain inner arg to round per PI/4
1056  */
1057 static void
1058 sp_spiral_outer_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint state)
1060     int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1062     SPSpiral *spiral = SP_SPIRAL(item);
1064     gdouble  dx = p[NR::X] - spiral->cx;
1065     gdouble  dy = p[NR::Y] - spiral->cy;
1067     if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1068         spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1069         if (!(state & GDK_MOD1_MASK)) {
1070             // if alt not pressed, change also rad; otherwise it is locked
1071             spiral->rad = MAX(hypot(dx, dy), 0.001);
1072         }
1073         if ( ( state & GDK_CONTROL_MASK )
1074              && snaps ) {
1075             spiral->arg = sp_round(spiral->arg, M_PI/snaps);
1076         }
1077     } else { // roll/unroll
1078         // arg of the spiral outer end
1079         double arg_1;
1080         sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
1082         // its fractional part after the whole turns are subtracted
1083         double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
1085         // arg of the mouse point relative to spiral center
1086         double mouse_angle = atan2(dy, dx);
1087         if (mouse_angle < 0)
1088             mouse_angle += 2*M_PI;
1090         // snap if ctrl
1091         if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1092             mouse_angle = sp_round(mouse_angle, M_PI/snaps);
1093         }
1095         // by how much we want to rotate the outer point
1096         double diff = mouse_angle - arg_r;
1097         if (diff > M_PI)
1098             diff -= 2*M_PI;
1099         else if (diff < -M_PI)
1100             diff += 2*M_PI;
1102         // calculate the new rad;
1103         // the value of t corresponding to the angle arg_1 + diff:
1104         double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
1105         // the rad at that t:
1106         double rad_new = 0;
1107         if (t_temp > spiral->t0)
1108             sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
1110         // change the revo (converting diff from radians to the number of turns)
1111         spiral->revo += diff/(2*M_PI);
1112         if (spiral->revo < 1e-3)
1113             spiral->revo = 1e-3;
1115         // if alt not pressed and the values are sane, change the rad
1116         if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
1117             // adjust t0 too so that the inner point stays unmoved
1118             double r0;
1119             sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
1120             spiral->rad = rad_new;
1121             spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
1122         }
1123         if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0;
1124         spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1125     }
1127     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1130 static NR::Point sp_spiral_inner_get(SPItem *item)
1132     SPSpiral *spiral = SP_SPIRAL(item);
1134     return sp_spiral_get_xy(spiral, spiral->t0);
1137 static NR::Point sp_spiral_outer_get(SPItem *item)
1139     SPSpiral *spiral = SP_SPIRAL(item);
1141     return sp_spiral_get_xy(spiral, 1.0);
1144 static void
1145 sp_spiral_inner_click(SPItem *item, guint state)
1147     SPSpiral *spiral = SP_SPIRAL(item);
1149     if (state & GDK_MOD1_MASK) {
1150         spiral->exp = 1;
1151         ((SPObject *)spiral)->updateRepr();
1152     } else if (state & GDK_SHIFT_MASK) {
1153         spiral->t0 = 0;
1154         ((SPObject *)spiral)->updateRepr();
1155     }
1158 static SPKnotHolder *
1159 sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop)
1161     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
1163     sp_knot_holder_add(knot_holder, sp_spiral_inner_set, sp_spiral_inner_get, sp_spiral_inner_click,
1164                        _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to converge/diverge"));
1165     sp_knot_holder_add(knot_holder, sp_spiral_outer_set, sp_spiral_outer_get, NULL,
1166                        _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to scale/rotate"));
1168     sp_pat_knot_holder(item, knot_holder);
1170     return knot_holder;
1173 /* SPOffset */
1175 static void
1176 sp_offset_offset_set(SPItem *item, NR::Point const &p, NR::Point const &/*origin*/, guint /*state*/)
1178     SPOffset *offset = SP_OFFSET(item);
1180     offset->rad = sp_offset_distance_to_original(offset, p);
1181     offset->knot = p;
1182     offset->knotSet = true;
1184     ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1188 static NR::Point sp_offset_offset_get(SPItem *item)
1190     SPOffset *offset = SP_OFFSET(item);
1192     NR::Point np;
1193     sp_offset_top_point(offset,&np);
1194     return np;
1197 static SPKnotHolder *
1198 sp_offset_knot_holder(SPItem *item, SPDesktop *desktop)
1200     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
1202     sp_knot_holder_add(knot_holder, sp_offset_offset_set, sp_offset_offset_get, NULL,
1203                        _("Adjust the <b>offset distance</b>"));
1205     sp_pat_knot_holder(item, knot_holder);
1207     return knot_holder;
1210 static SPKnotHolder *
1211 sp_misc_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, instead make a pattern-drag similar to gradient-drag
1213     if ((SP_OBJECT(item)->style->fill.isPaintserver())
1214         && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
1215     {
1216         SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
1218         sp_pat_knot_holder(item, knot_holder);
1220         return knot_holder;
1221     }
1222     return NULL;
1225 static void
1226 sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder)
1228     if ((SP_OBJECT(item)->style->fill.isPaintserver())
1229         && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
1230     {
1231         sp_knot_holder_add_full(knot_holder, sp_pattern_xy_set, sp_pattern_xy_get, NULL, SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,
1232                                 // TRANSLATORS: This refers to the pattern that's inside the object
1233                                 _("<b>Move</b> the pattern fill inside the object"));
1234         sp_knot_holder_add_full(knot_holder, sp_pattern_scale_set, sp_pattern_scale_get, NULL, SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
1235                                 _("<b>Scale</b> the pattern fill uniformly"));
1236         sp_knot_holder_add_full(knot_holder, sp_pattern_angle_set, sp_pattern_angle_get, NULL, SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
1237                                 _("<b>Rotate</b> the pattern fill; with <b>Ctrl</b> to snap angle"));
1238     }
1241 static NR::Point sp_flowtext_corner_get(SPItem *item)
1243     SPRect *rect = SP_RECT(item);
1245     return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1248 static void
1249 sp_flowtext_corner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
1251     SPRect *rect = SP_RECT(item);
1253     sp_rect_wh_set_internal(rect, p, origin, state);
1256 static SPKnotHolder *
1257 sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop)
1259     SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
1261     sp_knot_holder_add(knot_holder, sp_flowtext_corner_set, sp_flowtext_corner_get, NULL,
1262                        _("Drag to resize the <b>flowed text frame</b>"));
1264     return knot_holder;
1268 /*
1269   Local Variables:
1270   mode:c++
1271   c-file-style:"stroustrup"
1272   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1273   indent-tabs-mode:nil
1274   fill-column:99
1275   End:
1276 */
1277 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :