~vaifrax/inkscape/bugfix170049

« back to all changes in this revision

Viewing changes to src/object-edit.cpp

  • Committer: mental
  • Date: 2006-01-16 02:36:01 UTC
  • Revision ID: mental@users.sourceforge.net-20060116023601-wkr0h7edl5veyudq
moving trunk for module inkscape

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#define __SP_OBJECT_EDIT_C__
 
2
 
 
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
 */
 
12
 
 
13
#ifdef HAVE_CONFIG_H
 
14
# include "config.h"
 
15
#endif
 
16
 
 
17
 
 
18
 
 
19
#include "sp-item.h"
 
20
#include "sp-rect.h"
 
21
#include "sp-ellipse.h"
 
22
#include "sp-star.h"
 
23
#include "sp-spiral.h"
 
24
#include "sp-offset.h"
 
25
#include "sp-flowtext.h"
 
26
#include "prefs-utils.h"
 
27
#include "inkscape.h"
 
28
#include "snap.h"
 
29
#include "desktop-affine.h"
 
30
#include <style.h>
 
31
#include "desktop.h"
 
32
 
 
33
#include "sp-pattern.h"
 
34
#include "sp-path.h"
 
35
 
 
36
#include <glibmm/i18n.h>
 
37
 
 
38
#include "object-edit.h"
 
39
 
 
40
#include <libnr/nr-scale-ops.h>
 
41
 
 
42
 
 
43
#include "xml/repr.h"
 
44
 
 
45
#include "isnan.h"
 
46
 
 
47
#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
 
48
 
 
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);
 
57
 
 
58
SPKnotHolder *
 
59
sp_item_knot_holder(SPItem *item, SPDesktop *desktop)
 
60
{
 
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);
 
75
    }
 
76
 
 
77
    return NULL;
 
78
}
 
79
 
 
80
 
 
81
/* Pattern manipulation */
 
82
 
 
83
static gdouble sp_pattern_extract_theta(SPPattern *pat, gdouble scale)
 
84
{
 
85
    gdouble theta = asin(pat->patternTransform[1] / scale);
 
86
    if (pat->patternTransform[0] < 0) theta = M_PI - theta ;
 
87
    return theta;
 
88
}
 
89
 
 
90
static gdouble sp_pattern_extract_scale(SPPattern *pat)
 
91
{
 
92
    gdouble s = pat->patternTransform[1];
 
93
    gdouble c = pat->patternTransform[0];
 
94
    gdouble xscale = sqrt(c * c + s * s);
 
95
    return xscale;
 
96
}
 
97
 
 
98
static NR::Point sp_pattern_extract_trans(SPPattern const *pat)
 
99
{
 
100
    return NR::Point(pat->patternTransform[4], pat->patternTransform[5]);
 
101
}
 
102
 
 
103
static void
 
104
sp_pattern_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
105
{
 
106
    SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
107
 
 
108
    NR::Point p_snapped = p;
 
109
 
 
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];
 
113
        } else {
 
114
            p_snapped[NR::X] = origin[NR::X];
 
115
        }
 
116
    }
 
117
 
 
118
    if (state)  {
 
119
        NR::Point const q = p_snapped - sp_pattern_extract_trans(pat);
 
120
        sp_item_adjust_pattern(item, NR::Matrix(NR::translate(q)));
 
121
    }
 
122
 
 
123
    item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
124
}
 
125
 
 
126
 
 
127
static NR::Point sp_pattern_xy_get(SPItem *item)
 
128
{
 
129
    SPPattern const *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
130
    return sp_pattern_extract_trans(pat);
 
131
}
 
132
 
 
133
static NR::Point sp_pattern_angle_get(SPItem *item)
 
134
{
 
135
    SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
136
 
 
137
    gdouble x = (pattern_width(pat)*0.5);
 
138
    gdouble y = 0;
 
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);
 
144
    return delta;
 
145
}
 
146
 
 
147
static void
 
148
sp_pattern_angle_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
149
{
 
150
    int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
 
151
 
 
152
    SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
153
 
 
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);
 
157
 
 
158
    if ( state & GDK_CONTROL_MASK ) {
 
159
        theta = sp_round(theta, M_PI/snaps);
 
160
    }
 
161
 
 
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);
 
166
    rot[4] = t[NR::X];
 
167
    rot[5] = t[NR::Y];
 
168
    sp_item_adjust_pattern(item, rot, true);
 
169
    item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
170
}
 
171
 
 
172
static void
 
173
sp_pattern_scale_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
174
{
 
175
    SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
176
 
 
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;
 
184
 
 
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);
 
188
 
 
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);
 
191
    rot[4] = t[NR::X];
 
192
    rot[5] = t[NR::Y];
 
193
    sp_item_adjust_pattern(item, rot, true);
 
194
    item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
195
}
 
196
 
 
197
 
 
198
static NR::Point sp_pattern_scale_get(SPItem *item)
 
199
{
 
200
    SPPattern *pat = SP_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style));
 
201
 
 
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;
 
206
    a[4] = 0;
 
207
    a[5] = 0;
 
208
    delta = delta * a;
 
209
    delta = delta + sp_pattern_extract_trans(pat);
 
210
    return delta;
 
211
}
 
212
 
 
213
/* SPRect */
 
214
 
 
215
static NR::Point sp_rect_rx_get(SPItem *item)
 
216
{
 
217
    SPRect *rect = SP_RECT(item);
 
218
 
 
219
    return NR::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
 
220
}
 
221
 
 
222
static void sp_rect_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
223
{
 
224
    SPRect *rect = SP_RECT(item);
 
225
 
 
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;
 
230
        
 
231
    } else {
 
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;
 
234
    }
 
235
    
 
236
    ((SPObject*)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
237
}
 
238
 
 
239
 
 
240
static NR::Point sp_rect_ry_get(SPItem *item)
 
241
{
 
242
    SPRect *rect = SP_RECT(item);
 
243
 
 
244
    return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
 
245
}
 
246
 
 
247
static void sp_rect_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
248
{
 
249
    SPRect *rect = SP_RECT(item);
 
250
 
 
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;
 
255
    } else {
 
256
        if (!rect->rx._set || rect->rx.computed == 0) {
 
257
            rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
 
258
                                      0.0,
 
259
                                      MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
 
260
        } else {
 
261
            rect->ry.computed = CLAMP(p[NR::Y] - rect->y.computed,
 
262
                                      0.0,
 
263
                                      rect->height.computed / 2.0);
 
264
        }
 
265
 
 
266
        rect->ry._set = true;
 
267
    }
 
268
 
 
269
    ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
270
}
 
271
 
 
272
/**
 
273
 *  Remove rounding from a rectangle.
 
274
 */
 
275
static void rect_remove_rounding(SPRect *rect)
 
276
{
 
277
    SP_OBJECT_REPR(rect)->setAttribute("rx", NULL);
 
278
    SP_OBJECT_REPR(rect)->setAttribute("ry", NULL);
 
279
}
 
280
 
 
281
/**
 
282
 *  Called when the horizontal rounding radius knot is clicked.
 
283
 */
 
284
static void sp_rect_rx_knot_click(SPItem *item, guint state)
 
285
{
 
286
    SPRect *rect = SP_RECT(item);
 
287
 
 
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"));
 
293
    }
 
294
}
 
295
 
 
296
/**
 
297
 *  Called when the vertical rounding radius knot is clicked.
 
298
 */
 
299
static void sp_rect_ry_knot_click(SPItem *item, guint state)
 
300
{
 
301
    SPRect *rect = SP_RECT(item);
 
302
 
 
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"));
 
308
    }
 
309
}
 
310
 
 
311
#define SGN(x) ((x)>0?1:((x)<0?-1:0))
 
312
 
 
313
static void sp_rect_clamp_radii(SPRect *rect)
 
314
{
 
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;
 
319
    }
 
320
    if (2 * rect->ry.computed > rect->height.computed) {
 
321
        rect->ry.computed = 0.5 * rect->height.computed;
 
322
        rect->ry._set = true;
 
323
    }
 
324
}
 
325
 
 
326
static NR::Point sp_rect_wh_get(SPItem *item)
 
327
{
 
328
    SPRect *rect = SP_RECT(item);
 
329
 
 
330
    return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
 
331
}
 
332
 
 
333
static NR::Point rect_snap_knot_position(NR::Point const &p)
 
334
{
 
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);
 
340
}
 
341
 
 
342
static void sp_rect_wh_set_internal(SPRect *rect, NR::Point const &p, NR::Point const &origin, guint state)
 
343
{
 
344
    NR::Point const s = rect_snap_knot_position(p);
 
345
 
 
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);
 
350
 
 
351
        //original ratio
 
352
        gdouble const ratio = (w_orig / h_orig);
 
353
 
 
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];
 
357
 
 
358
        if (fabs(minx) > fabs(miny)) {
 
359
 
 
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);
 
365
            } else {
 
366
                // closer to the horizontal, change only width, height is h_orig
 
367
                rect->height.computed = MAX(h_orig, 0);
 
368
            }
 
369
 
 
370
        } else {
 
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);
 
376
            } else {
 
377
                // closer to the vertical, change only height, width is w_orig
 
378
                rect->width.computed = MAX(w_orig, 0);
 
379
            }
 
380
        }
 
381
 
 
382
        rect->width._set = rect->height._set = true;
 
383
 
 
384
    } else {
 
385
        // move freely
 
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;
 
389
    }
 
390
 
 
391
    sp_rect_clamp_radii(rect);
 
392
 
 
393
    ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
394
}
 
395
 
 
396
static void sp_rect_wh_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
397
{
 
398
    SPRect *rect = SP_RECT(item);
 
399
 
 
400
    sp_rect_wh_set_internal(rect, p, origin, state);
 
401
}
 
402
 
 
403
static NR::Point sp_rect_xy_get(SPItem *item)
 
404
{
 
405
    SPRect *rect = SP_RECT(item);
 
406
 
 
407
    return NR::Point(rect->x.computed, rect->y.computed);
 
408
}
 
409
 
 
410
static void sp_rect_xy_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
411
{
 
412
    SPRect *rect = SP_RECT(item);
 
413
 
 
414
    // opposite corner (unmoved)
 
415
    gdouble opposite_x = (rect->x.computed + rect->width.computed);
 
416
    gdouble opposite_y = (rect->y.computed + rect->height.computed);
 
417
 
 
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];
 
421
 
 
422
    NR::Point const s = rect_snap_knot_position(p);
 
423
 
 
424
    // mouse displacement since drag started
 
425
    gdouble minx = s[NR::X] - origin[NR::X];
 
426
    gdouble miny = s[NR::Y] - origin[NR::Y];
 
427
 
 
428
    if (state & GDK_CONTROL_MASK) {
 
429
        //original ratio
 
430
        gdouble ratio = (w_orig / h_orig);
 
431
 
 
432
        if (fabs(minx) > fabs(miny)) {
 
433
 
 
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);
 
441
            } else {
 
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);
 
445
            }
 
446
 
 
447
        } else {
 
448
 
 
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);
 
456
            } else {
 
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);
 
460
            }
 
461
 
 
462
        }
 
463
 
 
464
        rect->width._set = rect->height._set = rect->x._set = rect->y._set = true;
 
465
 
 
466
    } else {
 
467
        // move freely
 
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;
 
473
    }
 
474
 
 
475
    sp_rect_clamp_radii(rect);
 
476
 
 
477
    ((SPObject *)rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
478
}
 
479
 
 
480
static SPKnotHolder *sp_rect_knot_holder(SPItem *item, SPDesktop *desktop)
 
481
{
 
482
    SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
 
483
 
 
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 "
 
488
        "radius the same"));
 
489
 
 
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 "
 
494
        "radius the same")
 
495
      );
 
496
 
 
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")
 
502
      );
 
503
 
 
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")
 
509
      );
 
510
 
 
511
    sp_pat_knot_holder(item, knot_holder);
 
512
    return knot_holder;
 
513
}
 
514
 
 
515
/* SPArc */
 
516
 
 
517
/*
 
518
 * return values:
 
519
 *   1  : inside
 
520
 *   0  : on the curves
 
521
 *   -1 : outside
 
522
 */
 
523
static gint
 
524
sp_genericellipse_side(SPGenericEllipse *ellipse, NR::Point const &p)
 
525
{
 
526
    gdouble dx = (p[NR::X] - ellipse->cx.computed) / ellipse->rx.computed;
 
527
    gdouble dy = (p[NR::Y] - ellipse->cy.computed) / ellipse->ry.computed;
 
528
 
 
529
    gdouble s = dx * dx + dy * dy;
 
530
    if (s < 1.0) return 1;
 
531
    if (s > 1.0) return -1;
 
532
    return 0;
 
533
}
 
534
 
 
535
static void
 
536
sp_arc_start_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
537
{
 
538
    int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
 
539
 
 
540
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
541
    SPArc *arc = SP_ARC(item);
 
542
 
 
543
    ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
 
544
 
 
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 )
 
549
         && snaps )
 
550
    {
 
551
        ge->start = sp_round(ge->start, M_PI/snaps);
 
552
    }
 
553
    sp_genericellipse_normalize(ge);
 
554
    ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
555
}
 
556
 
 
557
static NR::Point sp_arc_start_get(SPItem *item)
 
558
{
 
559
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
560
    SPArc *arc = SP_ARC(item);
 
561
 
 
562
    return sp_arc_get_xy(arc, ge->start);
 
563
}
 
564
 
 
565
static void
 
566
sp_arc_end_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
567
{
 
568
    int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
 
569
 
 
570
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
571
    SPArc *arc = SP_ARC(item);
 
572
 
 
573
    ge->closed = (sp_genericellipse_side(ge, p) == -1) ? TRUE : FALSE;
 
574
 
 
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 )
 
579
         && snaps )
 
580
    {
 
581
        ge->end = sp_round(ge->end, M_PI/snaps);
 
582
    }
 
583
    sp_genericellipse_normalize(ge);
 
584
    ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
585
}
 
586
 
 
587
static NR::Point sp_arc_end_get(SPItem *item)
 
588
{
 
589
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
590
    SPArc *arc = SP_ARC(item);
 
591
 
 
592
    return sp_arc_get_xy(arc, ge->end);
 
593
}
 
594
 
 
595
static void
 
596
sp_arc_startend_click(SPItem *item, guint state)
 
597
{
 
598
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
599
 
 
600
    if (state & GDK_SHIFT_MASK) {
 
601
        ge->end = ge->start = 0;
 
602
        ((SPObject *)ge)->updateRepr();
 
603
    }
 
604
}
 
605
 
 
606
 
 
607
static void
 
608
sp_arc_rx_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
609
{
 
610
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
611
    SPArc *arc = SP_ARC(item);
 
612
 
 
613
    ge->rx.computed = fabs( ge->cx.computed - p[NR::X] );
 
614
 
 
615
    if ( state & GDK_CONTROL_MASK ) {
 
616
        ge->ry.computed = ge->rx.computed;
 
617
    }
 
618
 
 
619
    ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
620
}
 
621
 
 
622
static NR::Point sp_arc_rx_get(SPItem *item)
 
623
{
 
624
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
625
 
 
626
    return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(ge->rx.computed, 0));
 
627
}
 
628
 
 
629
static void
 
630
sp_arc_ry_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
631
{
 
632
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
633
    SPArc *arc = SP_ARC(item);
 
634
 
 
635
    ge->ry.computed = fabs( ge->cy.computed - p[NR::Y] );
 
636
 
 
637
    if ( state & GDK_CONTROL_MASK ) {
 
638
        ge->rx.computed = ge->ry.computed;
 
639
    }
 
640
 
 
641
    ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
642
}
 
643
 
 
644
static NR::Point sp_arc_ry_get(SPItem *item)
 
645
{
 
646
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
647
 
 
648
    return (NR::Point(ge->cx.computed, ge->cy.computed) -  NR::Point(0, ge->ry.computed));
 
649
}
 
650
 
 
651
static void
 
652
sp_arc_rx_click(SPItem *item, guint state)
 
653
{
 
654
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
655
 
 
656
    if (state & GDK_CONTROL_MASK) {
 
657
        ge->ry.computed = ge->rx.computed;
 
658
        ((SPObject *)ge)->updateRepr();
 
659
    }
 
660
}
 
661
 
 
662
static void
 
663
sp_arc_ry_click(SPItem *item, guint state)
 
664
{
 
665
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
 
666
 
 
667
    if (state & GDK_CONTROL_MASK) {
 
668
        ge->rx.computed = ge->ry.computed;
 
669
        ((SPObject *)ge)->updateRepr();
 
670
    }
 
671
}
 
672
 
 
673
static SPKnotHolder *
 
674
sp_arc_knot_holder(SPItem *item, SPDesktop *desktop)
 
675
{
 
676
    SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
 
677
 
 
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"));
 
690
 
 
691
    sp_pat_knot_holder(item, knot_holder);
 
692
 
 
693
    return knot_holder;
 
694
}
 
695
 
 
696
/* SPStar */
 
697
 
 
698
static void
 
699
sp_star_knot1_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
700
{
 
701
    SPStar *star = SP_STAR(item);
 
702
 
 
703
    NR::Point d = p - star->center;
 
704
 
 
705
    double arg1 = atan2(d);
 
706
    double darg1 = arg1 - star->arg[0];
 
707
 
 
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) {
 
713
        star->r[0]    = L2(d);
 
714
    } else {
 
715
        star->r[0]    = L2(d);
 
716
        star->arg[0]  = arg1;
 
717
        star->arg[1] += darg1;
 
718
    }
 
719
    ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
720
}
 
721
 
 
722
static void
 
723
sp_star_knot2_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
724
{
 
725
    SPStar *star = SP_STAR(item);
 
726
    if (star->flatsided == false) {
 
727
        NR::Point d = p - star->center;
 
728
 
 
729
        double arg1 = atan2(d);
 
730
        double darg1 = arg1 - star->arg[1];
 
731
 
 
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) {
 
737
            star->r[1]   = L2(d);
 
738
            star->arg[1] = star->arg[0] + M_PI / star->sides;
 
739
        }
 
740
        else {
 
741
            star->r[1]   = L2(d);
 
742
            star->arg[1] = atan2(d);
 
743
        }
 
744
        ((SPObject *)star)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
745
    }
 
746
}
 
747
 
 
748
static NR::Point sp_star_knot1_get(SPItem *item)
 
749
{
 
750
    g_assert(item != NULL);
 
751
 
 
752
    SPStar *star = SP_STAR(item);
 
753
 
 
754
    return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
 
755
 
 
756
}
 
757
 
 
758
static NR::Point sp_star_knot2_get(SPItem *item)
 
759
{
 
760
    g_assert(item != NULL);
 
761
 
 
762
    SPStar *star = SP_STAR(item);
 
763
 
 
764
    return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
 
765
}
 
766
 
 
767
static void
 
768
sp_star_knot_click(SPItem *item, guint state)
 
769
{
 
770
    SPStar *star = SP_STAR(item);
 
771
 
 
772
    if (state & GDK_MOD1_MASK) {
 
773
        star->randomized = 0;
 
774
        ((SPObject *)star)->updateRepr();
 
775
    } else if (state & GDK_SHIFT_MASK) {
 
776
        star->rounded = 0;
 
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();
 
781
    }
 
782
}
 
783
 
 
784
static SPKnotHolder *
 
785
sp_star_knot_holder(SPItem *item, SPDesktop *desktop)
 
786
{
 
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);
 
790
 
 
791
    SPStar *star = SP_STAR(item);
 
792
 
 
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"));
 
798
 
 
799
    sp_pat_knot_holder(item, knot_holder);
 
800
 
 
801
    return knot_holder;
 
802
}
 
803
 
 
804
/* SPSpiral */
 
805
 
 
806
/*
 
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
 
811
 */
 
812
static void
 
813
sp_spiral_inner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
814
{
 
815
    int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
 
816
 
 
817
    SPSpiral *spiral = SP_SPIRAL(item);
 
818
 
 
819
    gdouble   dx = p[NR::X] - spiral->cx;
 
820
    gdouble   dy = p[NR::Y] - spiral->cy;
 
821
 
 
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;
 
826
    } else {
 
827
        // roll/unroll from inside
 
828
        gdouble   arg_t0;
 
829
        sp_spiral_get_polar(spiral, spiral->t0, NULL, &arg_t0);
 
830
 
 
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);
 
834
 
 
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);
 
841
        }
 
842
 
 
843
        spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
 
844
    }
 
845
 
 
846
    ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
847
}
 
848
 
 
849
/*
 
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
 
853
 */
 
854
static void
 
855
sp_spiral_outer_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
856
{
 
857
    int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
 
858
 
 
859
    SPSpiral *spiral = SP_SPIRAL(item);
 
860
 
 
861
    gdouble  dx = p[NR::X] - spiral->cx;
 
862
    gdouble  dy = p[NR::Y] - spiral->cy;
 
863
 
 
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);
 
869
        }
 
870
        if ( ( state & GDK_CONTROL_MASK )
 
871
             && snaps ) {
 
872
            spiral->arg = sp_round(spiral->arg, M_PI/snaps);
 
873
        }
 
874
    } else { // roll/unroll
 
875
        // arg of the spiral outer end
 
876
        double arg_1;
 
877
        sp_spiral_get_polar(spiral, 1, NULL, &arg_1);
 
878
 
 
879
        // its fractional part after the whole turns are subtracted
 
880
        double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI);
 
881
 
 
882
        // arg of the mouse point relative to spiral center
 
883
        double mouse_angle = atan2(dy, dx);
 
884
        if (mouse_angle < 0)
 
885
            mouse_angle += 2*M_PI;
 
886
 
 
887
        // snap if ctrl
 
888
        if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
 
889
            mouse_angle = sp_round(mouse_angle, M_PI/snaps);
 
890
        }
 
891
 
 
892
        // by how much we want to rotate the outer point
 
893
        double diff = mouse_angle - arg_r;
 
894
        if (diff > M_PI)
 
895
            diff -= 2*M_PI;
 
896
        else if (diff < -M_PI)
 
897
            diff += 2*M_PI;
 
898
 
 
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:
 
903
        double rad_new = 0;
 
904
        if (t_temp > spiral->t0)
 
905
            sp_spiral_get_polar(spiral, t_temp, &rad_new, NULL);
 
906
 
 
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)
 
910
            spiral->revo = 1e-3;
 
911
 
 
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
 
915
            double r0;
 
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);
 
919
        }
 
920
        if (!isFinite(spiral->t0)) spiral->t0 = 0.0;
 
921
        spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
 
922
    }
 
923
 
 
924
    ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
925
}
 
926
 
 
927
static NR::Point sp_spiral_inner_get(SPItem *item)
 
928
{
 
929
    SPSpiral *spiral = SP_SPIRAL(item);
 
930
 
 
931
    return sp_spiral_get_xy(spiral, spiral->t0);
 
932
}
 
933
 
 
934
static NR::Point sp_spiral_outer_get(SPItem *item)
 
935
{
 
936
    SPSpiral *spiral = SP_SPIRAL(item);
 
937
 
 
938
    return sp_spiral_get_xy(spiral, 1.0);
 
939
}
 
940
 
 
941
static void
 
942
sp_spiral_inner_click(SPItem *item, guint state)
 
943
{
 
944
    SPSpiral *spiral = SP_SPIRAL(item);
 
945
 
 
946
    if (state & GDK_MOD1_MASK) {
 
947
        spiral->exp = 1;
 
948
        ((SPObject *)spiral)->updateRepr();
 
949
    } else if (state & GDK_SHIFT_MASK) {
 
950
        spiral->t0 = 0;
 
951
        ((SPObject *)spiral)->updateRepr();
 
952
    }
 
953
}
 
954
 
 
955
static SPKnotHolder *
 
956
sp_spiral_knot_holder(SPItem *item, SPDesktop *desktop)
 
957
{
 
958
    SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
 
959
 
 
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"));
 
964
 
 
965
    sp_pat_knot_holder(item, knot_holder);
 
966
 
 
967
    return knot_holder;
 
968
}
 
969
 
 
970
/* SPOffset */
 
971
 
 
972
static void
 
973
sp_offset_offset_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
974
{
 
975
    SPOffset *offset = SP_OFFSET(item);
 
976
 
 
977
    offset->rad = sp_offset_distance_to_original(offset, p);
 
978
    offset->knot = p;
 
979
    offset->knotSet = true;
 
980
 
 
981
    ((SPObject *)offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
 
982
}
 
983
 
 
984
 
 
985
static NR::Point sp_offset_offset_get(SPItem *item)
 
986
{
 
987
    SPOffset *offset = SP_OFFSET(item);
 
988
 
 
989
    NR::Point np;
 
990
    sp_offset_top_point(offset,&np);
 
991
    return np;
 
992
}
 
993
 
 
994
static SPKnotHolder *
 
995
sp_offset_knot_holder(SPItem *item, SPDesktop *desktop)
 
996
{
 
997
    SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
 
998
 
 
999
    sp_knot_holder_add(knot_holder, sp_offset_offset_set, sp_offset_offset_get, NULL,
 
1000
                       _("Adjust the <b>offset distance</b>"));
 
1001
 
 
1002
    sp_pat_knot_holder(item, knot_holder);
 
1003
 
 
1004
    return knot_holder;
 
1005
}
 
1006
 
 
1007
static SPKnotHolder *
 
1008
sp_path_knot_holder(SPItem *item, SPDesktop *desktop) // FIXME: eliminate, instead make a pattern-drag similar to gradient-drag
 
1009
{
 
1010
    if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
 
1011
        && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
 
1012
    {
 
1013
        SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, item, NULL);
 
1014
 
 
1015
        sp_pat_knot_holder(item, knot_holder);
 
1016
 
 
1017
        return knot_holder;
 
1018
    }
 
1019
    return NULL;
 
1020
}
 
1021
 
 
1022
static void
 
1023
sp_pat_knot_holder(SPItem *item, SPKnotHolder *knot_holder)
 
1024
{
 
1025
    if ((SP_OBJECT(item)->style->fill.type == SP_PAINT_TYPE_PAINTSERVER)
 
1026
        && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(SP_OBJECT(item)->style)))
 
1027
    {
 
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"));
 
1035
    }
 
1036
}
 
1037
 
 
1038
static NR::Point sp_flowtext_corner_get(SPItem *item)
 
1039
{
 
1040
    SPRect *rect = SP_RECT(item);
 
1041
 
 
1042
    return NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
 
1043
}
 
1044
 
 
1045
static void
 
1046
sp_flowtext_corner_set(SPItem *item, NR::Point const &p, NR::Point const &origin, guint state)
 
1047
{
 
1048
    SPRect *rect = SP_RECT(item);
 
1049
 
 
1050
    sp_rect_wh_set_internal(rect, p, origin, state);
 
1051
}
 
1052
 
 
1053
static SPKnotHolder *
 
1054
sp_flowtext_knot_holder(SPItem *item, SPDesktop *desktop)
 
1055
{
 
1056
    SPKnotHolder *knot_holder = sp_knot_holder_new(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL);
 
1057
 
 
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>"));
 
1060
 
 
1061
    return knot_holder;
 
1062
}
 
1063
 
 
1064
 
 
1065
/*
 
1066
  Local Variables:
 
1067
  mode:c++
 
1068
  c-file-style:"stroustrup"
 
1069
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
 
1070
  indent-tabs-mode:nil
 
1071
  fill-column:99
 
1072
  End:
 
1073
*/
 
1074
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :