1
#define __SP_OBJECT_EDIT_C__
4
* Node editing extension to objects
7
* Lauris Kaplinski <lauris@kaplinski.com>
10
* Licensed under GNU GPL
21
#include "sp-ellipse.h"
23
#include "sp-spiral.h"
24
#include "sp-offset.h"
25
#include "sp-flowtext.h"
26
#include "prefs-utils.h"
29
#include "desktop-affine.h"
33
#include "sp-pattern.h"
36
#include <glibmm/i18n.h>
38
#include "object-edit.h"
40
#include <libnr/nr-scale-ops.h>
47
#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
49
static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop);
50
static SPKnotHolder *sp_arc_knot_holder(SPItem *item, SPDesktop *desktop);
51
static SPKnotHolder *sp_star_knot_holder(SPItem *item, SPDesktop *desktop);
52
static SPKnotHolder *sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop);
53
static SPKnotHolder *sp_offset_knot_holder(SPItem *item, SPDesktop *desktop);
54
static SPKnotHolder *sp_path_knot_holder(SPItem *item, SPDesktop *desktop);
55
static SPKnotHolder *sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop);
56
static void sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder);
59
sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
61
if (SP_IS_RECT(item)) {
62
return sp_rect_knot_holder(item, desktop);
63
} else if (SP_IS_ARC(item)) {
64
return sp_arc_knot_holder(item, desktop);
65
} else if (SP_IS_STAR(item)) {
66
return sp_star_knot_holder(item, desktop);
67
} else if (SP_IS_SPIRAL(item)) {
68
return sp_spiral_knot_holder(item, desktop);
69
} else if (SP_IS_OFFSET(item)) {
70
return sp_offset_knot_holder(item, desktop);
71
} else if (SP_IS_PATH(item)) {
72
return sp_path_knot_holder(item, desktop);
73
} else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
74
return sp_flowtext_knot_holder(item, desktop);
81
/* Pattern manipulation */
83
static gdouble sp_pattern_extract_theta(SPPattern *pat, gdouble scale)
85
gdouble theta = asin(pat->patternTransform[1] / scale);
86
if (pat->patternTransform[0] < 0) theta = M_PI - theta ;
90
static gdouble sp_pattern_extract_scale(SPPattern *pat)
92
gdouble s = pat->patternTransform[1];
93
gdouble c = pat->patternTransform[0];
94
gdouble xscale = sqrt(c * c + s * s);
98
static NR::Point sp_pattern_extract_trans(SPPattern const *pat)
100
return NR::Point(pat->patternTransform[4], pat->patternTransform[5]);
104
sp_pattern_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
106
SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
108
NR::Point p_snapped = p;
110
if ( state & GDK_CONTROL_MASK ) {
111
if (fabs((p - origin)[NR::X]) > fabs((p - origin)[NR::Y])) {
112
p_snapped[NR::Y] = origin[NR::Y];
114
p_snapped[NR::X] = origin[NR::X];
119
NR::Point const q = p_snapped - sp_pattern_extract_trans(pat);
120
sp_item_adjust_pattern(item, NR::Matrix(NR::translate(q)));
123
item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
127
static NR::Point sp_pattern_xy_get(SPItem *item)
129
SPPattern const *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
130
return sp_pattern_extract_trans(pat);
133
static NR::Point sp_pattern_angle_get(SPItem *item)
135
SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
137
gdouble x = (pattern_width(pat)*0.5);
139
NR::Point delta = NR::Point(x,y);
140
gdouble scale = sp_pattern_extract_scale(pat);
141
gdouble theta = sp_pattern_extract_theta(pat, scale);
142
delta = delta * NR::Matrix(NR::rotate(theta))*NR::Matrix(NR::scale(scale,scale));
143
delta = delta + sp_pattern_extract_trans(pat);
148
sp_pattern_angle_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
150
int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
152
SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
154
// get the angle from pattern 0,0 to the cursor pos
155
NR::Point delta = p - sp_pattern_extract_trans(pat);
156
gdouble theta = atan2(delta);
158
if ( state & GDK_CONTROL_MASK ) {
159
theta = sp_round(theta, M_PI/snaps);
162
// get the scale from the current transform so we can keep it.
163
gdouble scl = sp_pattern_extract_scale(pat);
164
NR::Matrix rot = NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl));
165
NR::Point const t = sp_pattern_extract_trans(pat);
168
sp_item_adjust_pattern(item, rot, true);
169
item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
173
sp_pattern_scale_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
175
SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
177
// Get the scale from the position of the knotholder,
178
NR::Point d = p - sp_pattern_extract_trans(pat);
179
gdouble s = NR::L2(d);
180
gdouble pat_x = pattern_width(pat) * 0.5;
181
gdouble pat_y = pattern_height(pat) * 0.5;
182
gdouble pat_h = hypot(pat_x, pat_y);
183
gdouble scl = s / pat_h;
185
// get angle from current transform, (need get current scale first to calculate angle)
186
gdouble oldscale = sp_pattern_extract_scale(pat);
187
gdouble theta = sp_pattern_extract_theta(pat,oldscale);
189
NR::Matrix rot = NR::Matrix(NR::rotate(theta)) * NR::Matrix(NR::scale(scl,scl));
190
NR::Point const t = sp_pattern_extract_trans(pat);
193
sp_item_adjust_pattern(item, rot, true);
194
item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
198
static NR::Point sp_pattern_scale_get(SPItem *item)
200
SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
202
gdouble x = pattern_width(pat)*0.5;
203
gdouble y = pattern_height(pat)*0.5;
204
NR::Point delta = NR::Point(x,y);
205
NR::Matrix a = pat->patternTransform;
209
delta = delta + sp_pattern_extract_trans(pat);
215
static NR::Point sp_rect_rx_get(SPItem *item)
217
SPRect *rect = SP_RECT(item);
219
return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
222
static void sp_rect_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
224
SPRect *rect = SP_RECT(item);
226
if (state & GDK_CONTROL_MASK) {
227
gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
228
rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, temp);
229
rect->rx._set = rect->ry._set = true;
232
rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - p[NR::X], 0.0, rect->width.computed / 2.0);
233
rect->rx._set = true;
236
((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
240
static NR::Point sp_rect_ry_get(SPItem *item)
242
SPRect *rect = SP_RECT(item);
244
return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
247
static void sp_rect_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
249
SPRect *rect = SP_RECT(item);
251
if (state & GDK_CONTROL_MASK) {
252
gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
253
rect->rx.computed = rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed, 0.0, temp);
254
rect->ry._set = rect->rx._set = true;
256
if (!rect->rx._set || rect->rx.computed == 0) {
257
rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
259
MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
261
rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
263
rect->height.computed / 2.0);
266
rect->ry._set = true;
269
((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
273
* Remove rounding from a rectangle.
275
static void rect_remove_rounding(SPRect *rect)
277
SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
278
SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
282
* Called when the horizontal rounding radius knot is clicked.
284
static void sp_rect_rx_knot_click(SPItem *item, guint state)
286
SPRect *rect = SP_RECT(item);
288
if (state & GDK_SHIFT_MASK) {
289
rect_remove_rounding(rect);
290
} else if (state & GDK_CONTROL_MASK) {
291
/* Ctrl-click sets the vertical rounding to be the same as the horizontal */
292
SP_OBJECT_REPR(rect)->setAttribute("ry", SP_OBJECT_REPR(rect)->attribute("rx"));
297
* Called when the vertical rounding radius knot is clicked.
299
static void sp_rect_ry_knot_click(SPItem *item, guint state)
301
SPRect *rect = SP_RECT(item);
303
if (state & GDK_SHIFT_MASK) {
304
rect_remove_rounding(rect);
305
} else if (state & GDK_CONTROL_MASK) {
306
/* Ctrl-click sets the vertical rounding to be the same as the horizontal */
307
SP_OBJECT_REPR(rect)->setAttribute("rx", SP_OBJECT_REPR(rect)->attribute("ry"));
311
#define SGN(x) ((x)>0?1:((x)<0?-1:0))
313
static void sp_rect_clamp_radii(SPRect *rect)
315
// clamp rounding radii so that they do not exceed width/height
316
if (2 * rect->rx.computed > rect->width.computed) {
317
rect->rx.computed = 0.5 * rect->width.computed;
318
rect->rx._set = true;
320
if (2 * rect->ry.computed > rect->height.computed) {
321
rect->ry.computed = 0.5 * rect->height.computed;
322
rect->ry._set = true;
326
static NR::Point sp_rect_wh_get(SPItem *item)
328
SPRect *rect = SP_RECT(item);
330
return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
333
static NR::Point rect_snap_knot_position(NR::Point const &p)
335
SPDesktop const *desktop = inkscape_active_desktop();
336
NR::Point s = sp_desktop_dt2root_xy_point(desktop, p);
337
SnapManager const m(desktop->namedview);
338
s = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, s, NULL).getPoint();
339
return sp_desktop_root2dt_xy_point(desktop, s);
342
static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
344
NR::Point const s = rect_snap_knot_position(p);
346
if (state & GDK_CONTROL_MASK) {
347
// original width/height when drag started
348
gdouble const w_orig = (origin[NR::X] - rect->x.computed);
349
gdouble const h_orig = (origin[NR::Y] - rect->y.computed);
352
gdouble const ratio = (w_orig / h_orig);
354
// mouse displacement since drag started
355
gdouble const minx = s[NR::X] - origin[NR::X];
356
gdouble const miny = s[NR::Y] - origin[NR::Y];
358
if (fabs(minx) > fabs(miny)) {
360
// snap to horizontal or diagonal
361
rect->width.computed = MAX(w_orig + minx, 0);
362
if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
363
// closer to the diagonal and in same-sign quarters, change both using ratio
364
rect->height.computed = MAX(h_orig + minx / ratio, 0);
366
// closer to the horizontal, change only width, height is h_orig
367
rect->height.computed = MAX(h_orig, 0);
371
// snap to vertical or diagonal
372
rect->height.computed = MAX(h_orig + miny, 0);
373
if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
374
// closer to the diagonal and in same-sign quarters, change both using ratio
375
rect->width.computed = MAX(w_orig + miny * ratio, 0);
377
// closer to the vertical, change only height, width is w_orig
378
rect->width.computed = MAX(w_orig, 0);
382
rect->width._set = rect->height._set = true;
386
rect->width.computed = MAX(s[NR::X] - rect->x.computed, 0);
387
rect->height.computed = MAX(s[NR::Y] - rect->y.computed, 0);
388
rect->width._set = rect->height._set = true;
391
sp_rect_clamp_radii(rect);
393
((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
396
static void sp_rect_wh_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
398
SPRect *rect = SP_RECT(item);
400
sp_rect_wh_set_internal(rect, p, origin, state);
403
static NR::Point sp_rect_xy_get(SPItem *item)
405
SPRect *rect = SP_RECT(item);
407
return NR::Point(rect->x.computed, rect->y.computed);
410
static void sp_rect_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
412
SPRect *rect = SP_RECT(item);
414
// opposite corner (unmoved)
415
gdouble opposite_x = (rect->x.computed + rect->width.computed);
416
gdouble opposite_y = (rect->y.computed + rect->height.computed);
418
// original width/height when drag started
419
gdouble w_orig = opposite_x - origin[NR::X];
420
gdouble h_orig = opposite_y - origin[NR::Y];
422
NR::Point const s = rect_snap_knot_position(p);
424
// mouse displacement since drag started
425
gdouble minx = s[NR::X] - origin[NR::X];
426
gdouble miny = s[NR::Y] - origin[NR::Y];
428
if (state & GDK_CONTROL_MASK) {
430
gdouble ratio = (w_orig / h_orig);
432
if (fabs(minx) > fabs(miny)) {
434
// snap to horizontal or diagonal
435
rect->x.computed = MIN(s[NR::X], opposite_x);
436
rect->width.computed = MAX(w_orig - minx, 0);
437
if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
438
// closer to the diagonal and in same-sign quarters, change both using ratio
439
rect->y.computed = MIN(origin[NR::Y] + minx / ratio, opposite_y);
440
rect->height.computed = MAX(h_orig - minx / ratio, 0);
442
// closer to the horizontal, change only width, height is h_orig
443
rect->y.computed = MIN(origin[NR::Y], opposite_y);
444
rect->height.computed = MAX(h_orig, 0);
449
// snap to vertical or diagonal
450
rect->y.computed = MIN(s[NR::Y], opposite_y);
451
rect->height.computed = MAX(h_orig - miny, 0);
452
if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
453
// closer to the diagonal and in same-sign quarters, change both using ratio
454
rect->x.computed = MIN(origin[NR::X] + miny * ratio, opposite_x);
455
rect->width.computed = MAX(w_orig - miny * ratio, 0);
457
// closer to the vertical, change only height, width is w_orig
458
rect->x.computed = MIN(origin[NR::X], opposite_x);
459
rect->width.computed = MAX(w_orig, 0);
464
rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
468
rect->x.computed = MIN(s[NR::X], opposite_x);
469
rect->width.computed = MAX(w_orig - minx, 0);
470
rect->y.computed = MIN(s[NR::Y], opposite_y);
471
rect->height.computed = MAX(h_orig - miny, 0);
472
rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
475
sp_rect_clamp_radii(rect);
477
((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
480
static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop)
482
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
484
sp_knot_holder_add_full(
485
knot_holder, sp_rect_rx_set, sp_rect_rx_get, sp_rect_rx_knot_click,
486
SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
487
_("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> to make the vertical "
490
sp_knot_holder_add_full(
491
knot_holder, sp_rect_ry_set, sp_rect_ry_get, sp_rect_ry_knot_click,
492
SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
493
_("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> to make the horizontal "
497
sp_knot_holder_add_full(
498
knot_holder, sp_rect_wh_set, sp_rect_wh_get, NULL,
499
SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
500
_("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> to lock ratio "
501
"or stretch in one dimension only")
504
sp_knot_holder_add_full(
505
knot_holder, sp_rect_xy_set, sp_rect_xy_get, NULL,
506
SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
507
_("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> to lock ratio "
508
"or stretch in one dimension only")
511
sp_pat_knot_holder(item, knot_holder);
524
sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
526
gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
527
gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
529
gdouble s = dx * dx + dy * dy;
530
if (s < 1.0) return 1;
531
if (s > 1.0) return -1;
536
sp_arc_start_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
538
int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
540
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
541
SPArc *arc = SP_ARC(item);
543
ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
545
NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
546
NR::scale sc(ge->rx.computed, ge->ry.computed);
547
ge->start = atan2(delta * sc.inverse());
548
if ( ( state & GDK_CONTROL_MASK )
551
ge->start = sp_round(ge->start, M_PI/snaps);
553
sp_genericellipse_normalize(ge);
554
((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
557
static NR::Point sp_arc_start_get(SPItem *item)
559
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
560
SPArc *arc = SP_ARC(item);
562
return sp_arc_get_xy(arc, ge->start);
566
sp_arc_end_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
568
int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
570
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
571
SPArc *arc = SP_ARC(item);
573
ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
575
NR::Point delta = p - NR::Point(ge->cx.computed, ge->cy.computed);
576
NR::scale sc(ge->rx.computed, ge->ry.computed);
577
ge->end = atan2(delta * sc.inverse());
578
if ( ( state & GDK_CONTROL_MASK )
581
ge->end = sp_round(ge->end, M_PI/snaps);
583
sp_genericellipse_normalize(ge);
584
((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
587
static NR::Point sp_arc_end_get(SPItem *item)
589
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
590
SPArc *arc = SP_ARC(item);
592
return sp_arc_get_xy(arc, ge->end);
596
sp_arc_startend_click(SPItem *item, guint state)
598
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
600
if (state & GDK_SHIFT_MASK) {
601
ge->end = ge->start = 0;
602
((SPObject *)ge)->updateRepr();
608
sp_arc_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
610
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
611
SPArc *arc = SP_ARC(item);
613
ge->rx.computed = fabs( ge->cx.computed - p[NR::X] );
615
if ( state & GDK_CONTROL_MASK ) {
616
ge->ry.computed = ge->rx.computed;
619
((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
622
static NR::Point sp_arc_rx_get(SPItem *item)
624
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
626
return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(ge->rx.computed, 0));
630
sp_arc_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
632
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
633
SPArc *arc = SP_ARC(item);
635
ge->ry.computed = fabs( ge->cy.computed - p[NR::Y] );
637
if ( state & GDK_CONTROL_MASK ) {
638
ge->rx.computed = ge->ry.computed;
641
((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
644
static NR::Point sp_arc_ry_get(SPItem *item)
646
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
648
return (NR::Point(ge->cx.computed, ge->cy.computed) - NR::Point(0, ge->ry.computed));
652
sp_arc_rx_click(SPItem *item, guint state)
654
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
656
if (state & GDK_CONTROL_MASK) {
657
ge->ry.computed = ge->rx.computed;
658
((SPObject *)ge)->updateRepr();
663
sp_arc_ry_click(SPItem *item, guint state)
665
SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
667
if (state & GDK_CONTROL_MASK) {
668
ge->rx.computed = ge->ry.computed;
669
((SPObject *)ge)->updateRepr();
673
static SPKnotHolder *
674
sp_arc_knot_holder(SPItem *item, SPDesktop *desktop)
676
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
678
sp_knot_holder_add_full(knot_holder, sp_arc_rx_set, sp_arc_rx_get, sp_arc_rx_click,
679
SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
680
_("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"));
681
sp_knot_holder_add_full(knot_holder, sp_arc_ry_set, sp_arc_ry_get, sp_arc_ry_click,
682
SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
683
_("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"));
684
sp_knot_holder_add_full(knot_holder, sp_arc_start_set, sp_arc_start_get, sp_arc_startend_click,
685
SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
686
_("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"));
687
sp_knot_holder_add_full(knot_holder, sp_arc_end_set, sp_arc_end_get, sp_arc_startend_click,
688
SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
689
_("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"));
691
sp_pat_knot_holder(item, knot_holder);
699
sp_star_knot1_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
701
SPStar *star = SP_STAR(item);
703
NR::Point d = p - star->center;
705
double arg1 = atan2(d);
706
double darg1 = arg1 - star->arg[0];
708
if (state & GDK_MOD1_MASK) {
709
star->randomized = darg1/(star->arg[0] - star->arg[1]);
710
} else if (state & GDK_SHIFT_MASK) {
711
star->rounded = darg1/(star->arg[0] - star->arg[1]);
712
} else if (state & GDK_CONTROL_MASK) {
717
star->arg[1] += darg1;
719
((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
723
sp_star_knot2_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
725
SPStar *star = SP_STAR(item);
726
if (star->flatsided == false) {
727
NR::Point d = p - star->center;
729
double arg1 = atan2(d);
730
double darg1 = arg1 - star->arg[1];
732
if (state & GDK_MOD1_MASK) {
733
star->randomized = darg1/(star->arg[0] - star->arg[1]);
734
} else if (state & GDK_SHIFT_MASK) {
735
star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
736
} else if (state & GDK_CONTROL_MASK) {
738
star->arg[1] = star->arg[0] + M_PI / star->sides;
742
star->arg[1] = atan2(d);
744
((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
748
static NR::Point sp_star_knot1_get(SPItem *item)
750
g_assert(item != NULL);
752
SPStar *star = SP_STAR(item);
754
return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
758
static NR::Point sp_star_knot2_get(SPItem *item)
760
g_assert(item != NULL);
762
SPStar *star = SP_STAR(item);
764
return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
768
sp_star_knot_click(SPItem *item, guint state)
770
SPStar *star = SP_STAR(item);
772
if (state & GDK_MOD1_MASK) {
773
star->randomized = 0;
774
((SPObject *)star)->updateRepr();
775
} else if (state & GDK_SHIFT_MASK) {
777
((SPObject *)star)->updateRepr();
778
} else if (state & GDK_CONTROL_MASK) {
779
star->arg[1] = star->arg[0] + M_PI / star->sides;
780
((SPObject *)star)->updateRepr();
784
static SPKnotHolder *
785
sp_star_knot_holder(SPItem *item, SPDesktop *desktop)
787
/* we don't need to get parent knot_holder */
788
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
789
g_assert(item != NULL);
791
SPStar *star = SP_STAR(item);
793
sp_knot_holder_add(knot_holder, sp_star_knot1_set, sp_star_knot1_get, sp_star_knot_click,
794
_("Adjust the <b>tip radius</b> of the star or polygon; with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
795
if (star->flatsided == false)
796
sp_knot_holder_add(knot_holder, sp_star_knot2_set, sp_star_knot2_get, sp_star_knot_click,
797
_("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"));
799
sp_pat_knot_holder(item, knot_holder);
807
* set attributes via inner (t=t0) knot point:
808
* [default] increase/decrease inner point
809
* [shift] increase/decrease inner and outer arg synchronizely
810
* [control] constrain inner arg to round per PI/4
813
sp_spiral_inner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
815
int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
817
SPSpiral *spiral = SP_SPIRAL(item);
819
gdouble dx = p[NR::X] - spiral->cx;
820
gdouble dy = p[NR::Y] - spiral->cy;
822
if (state & GDK_MOD1_MASK) {
823
// adjust divergence by vertical drag, relative to rad
824
double new_exp = (spiral->rad + dy)/(spiral->rad);
825
spiral->exp = new_exp > 0? new_exp : 0;
827
// roll/unroll from inside
829
sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
831
gdouble arg_tmp = atan2(dy, dx) - arg_t0;
832
gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
833
spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
835
/* round inner arg per PI/snaps, if CTRL is pressed */
836
if ( ( state & GDK_CONTROL_MASK )
837
&& ( fabs(spiral->revo) > SP_EPSILON_2 )
838
&& ( snaps != 0 ) ) {
839
gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
840
spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo);
843
spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
846
((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
850
* set attributes via outer (t=1) knot point:
851
* [default] increase/decrease revolution factor
852
* [control] constrain inner arg to round per PI/4
855
sp_spiral_outer_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
857
int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
859
SPSpiral *spiral = SP_SPIRAL(item);
861
gdouble dx = p[NR::X] - spiral->cx;
862
gdouble dy = p[NR::Y] - spiral->cy;
864
if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
865
spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
866
if (!(state & GDK_MOD1_MASK)) {
867
// if alt not pressed, change also rad; otherwise it is locked
868
spiral->rad = MAX(hypot(dx, dy), 0.001);
870
if ( ( state & GDK_CONTROL_MASK )
872
spiral->arg = sp_round(spiral->arg, M_PI/snaps);
874
} else { // roll/unroll
875
// arg of the spiral outer end
877
sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
879
// its fractional part after the whole turns are subtracted
880
double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
882
// arg of the mouse point relative to spiral center
883
double mouse_angle = atan2(dy, dx);
885
mouse_angle += 2*M_PI;
888
if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
889
mouse_angle = sp_round(mouse_angle, M_PI/snaps);
892
// by how much we want to rotate the outer point
893
double diff = mouse_angle - arg_r;
896
else if (diff < -M_PI)
899
// calculate the new rad;
900
// the value of t corresponding to the angle arg_1 + diff:
901
double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo);
902
// the rad at that t:
904
if (t_temp > spiral->t0)
905
sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
907
// change the revo (converting diff from radians to the number of turns)
908
spiral->revo += diff/(2*M_PI);
909
if (spiral->revo < 1e-3)
912
// if alt not pressed and the values are sane, change the rad
913
if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
914
// adjust t0 too so that the inner point stays unmoved
916
sp_spiral_get_polar(spiral, spiral->t0, &r0, NULL);
917
spiral->rad = rad_new;
918
spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
920
if (!isFinite(spiral->t0)) spiral->t0 = 0.0;
921
spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
924
((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
927
static NR::Point sp_spiral_inner_get(SPItem *item)
929
SPSpiral *spiral = SP_SPIRAL(item);
931
return sp_spiral_get_xy(spiral, spiral->t0);
934
static NR::Point sp_spiral_outer_get(SPItem *item)
936
SPSpiral *spiral = SP_SPIRAL(item);
938
return sp_spiral_get_xy(spiral, 1.0);
942
sp_spiral_inner_click(SPItem *item, guint state)
944
SPSpiral *spiral = SP_SPIRAL(item);
946
if (state & GDK_MOD1_MASK) {
948
((SPObject *)spiral)->updateRepr();
949
} else if (state & GDK_SHIFT_MASK) {
951
((SPObject *)spiral)->updateRepr();
955
static SPKnotHolder *
956
sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop)
958
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
960
sp_knot_holder_add(knot_holder, sp_spiral_inner_set, sp_spiral_inner_get, sp_spiral_inner_click,
961
_("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to converge/diverge"));
962
sp_knot_holder_add(knot_holder, sp_spiral_outer_set, sp_spiral_outer_get, NULL,
963
_("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to scale/rotate"));
965
sp_pat_knot_holder(item, knot_holder);
973
sp_offset_offset_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
975
SPOffset *offset = SP_OFFSET(item);
977
offset->rad = sp_offset_distance_to_original(offset, p);
979
offset->knotSet = true;
981
((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
985
static NR::Point sp_offset_offset_get(SPItem *item)
987
SPOffset *offset = SP_OFFSET(item);
990
sp_offset_top_point(offset,&np);
994
static SPKnotHolder *
995
sp_offset_knot_holder(SPItem *item, SPDesktop *desktop)
997
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
999
sp_knot_holder_add(knot_holder, sp_offset_offset_set, sp_offset_offset_get, NULL,
1000
_("Adjust the <b>offset distance</b>"));
1002
sp_pat_knot_holder(item, knot_holder);
1007
static SPKnotHolder *
1008
sp_path_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, instead make a pattern-drag similar to gradient-drag
1010
if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
1011
&& SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
1013
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
1015
sp_pat_knot_holder(item, knot_holder);
1023
sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder)
1025
if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
1026
&& SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
1028
sp_knot_holder_add_full(knot_holder, sp_pattern_xy_set, sp_pattern_xy_get, NULL, SP_KNOT_SHAPE_CROSS, SP_KNOT_MODE_XOR,
1029
// TRANSLATORS: This refers to the pattern that's inside the object
1030
_("<b>Move</b> the pattern fill inside the object"));
1031
sp_knot_holder_add_full(knot_holder, sp_pattern_scale_set, sp_pattern_scale_get, NULL, SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR,
1032
_("<b>Scale</b> the pattern fill uniformly"));
1033
sp_knot_holder_add_full(knot_holder, sp_pattern_angle_set, sp_pattern_angle_get, NULL, SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR,
1034
_("<b>Rotate</b> the pattern fill; with <b>Ctrl</b> to snap angle"));
1038
static NR::Point sp_flowtext_corner_get(SPItem *item)
1040
SPRect *rect = SP_RECT(item);
1042
return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
1046
sp_flowtext_corner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
1048
SPRect *rect = SP_RECT(item);
1050
sp_rect_wh_set_internal(rect, p, origin, state);
1053
static SPKnotHolder *
1054
sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop)
1056
SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
1058
sp_knot_holder_add(knot_holder, sp_flowtext_corner_set, sp_flowtext_corner_get, NULL,
1059
_("Drag to resize the <b>flowed text frame</b>"));
1068
c-file-style:"stroustrup"
1069
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1070
indent-tabs-mode:nil
1074
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :