1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
4 * Abhishek Sharma
5 *
6 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
7 */
8
9 #ifdef HAVE_CONFIG_H
10 # include "config.h" // only include where actually required!
11 #endif
12
13 //#define LPE_ENABLE_TEST_EFFECTS //uncomment for toy effects
14
15 // include effects:
16 #include "live_effects/lpe-angle_bisector.h"
17 #include "live_effects/lpe-attach-path.h"
18 #include "live_effects/lpe-bendpath.h"
19 #include "live_effects/lpe-bool.h"
20 #include "live_effects/lpe-bounding-box.h"
21 #include "live_effects/lpe-bspline.h"
22 #include "live_effects/lpe-circle_3pts.h"
23 #include "live_effects/lpe-circle_with_radius.h"
24 #include "live_effects/lpe-clone-original.h"
25 #include "live_effects/lpe-constructgrid.h"
26 #include "live_effects/lpe-copy_rotate.h"
27 #include "live_effects/lpe-curvestitch.h"
28 #include "live_effects/lpe-dashed-stroke.h"
29 #include "live_effects/lpe-dynastroke.h"
30 #include "live_effects/lpe-ellipse_5pts.h"
31 #include "live_effects/lpe-embrodery-stitch.h"
32 #include "live_effects/lpe-envelope.h"
33 #include "live_effects/lpe-extrude.h"
34 #include "live_effects/lpe-fill-between-many.h"
35 #include "live_effects/lpe-fill-between-strokes.h"
36 #include "live_effects/lpe-fillet-chamfer.h"
37 #include "live_effects/lpe-gears.h"
38 #include "live_effects/lpe-interpolate.h"
39 #include "live_effects/lpe-interpolate_points.h"
40 #include "live_effects/lpe-jointype.h"
41 #include "live_effects/lpe-knot.h"
42 #include "live_effects/lpe-lattice.h"
43 #include "live_effects/lpe-lattice2.h"
44 #include "live_effects/lpe-line_segment.h"
45 #include "live_effects/lpe-measure-segments.h"
46 #include "live_effects/lpe-mirror_symmetry.h"
47 #include "live_effects/lpe-offset.h"
48 #include "live_effects/lpe-parallel.h"
49 #include "live_effects/lpe-path_length.h"
50 #include "live_effects/lpe-patternalongpath.h"
51 #include "live_effects/lpe-perp_bisector.h"
52 #include "live_effects/lpe-perspective-envelope.h"
53 #include "live_effects/lpe-powerclip.h"
54 #include "live_effects/lpe-powermask.h"
55 #include "live_effects/lpe-powerstroke.h"
56 #include "live_effects/lpe-pts2ellipse.h"
57 #include "live_effects/lpe-recursiveskeleton.h"
58 #include "live_effects/lpe-rough-hatches.h"
59 #include "live_effects/lpe-roughen.h"
60 #include "live_effects/lpe-ruler.h"
61 #include "live_effects/lpe-show_handles.h"
62 #include "live_effects/lpe-simplify.h"
63 #include "live_effects/lpe-sketch.h"
64 #include "live_effects/lpe-slice.h"
65 #include "live_effects/lpe-spiro.h"
66 #include "live_effects/lpe-tangent_to_curve.h"
67 #include "live_effects/lpe-taperstroke.h"
68 #include "live_effects/lpe-test-doEffect-stack.h"
69 #include "live_effects/lpe-text_label.h"
70 #include "live_effects/lpe-transform_2pts.h"
71 #include "live_effects/lpe-vonkoch.h"
72
73 #include "live_effects/lpeobject.h"
74
75 #include "xml/node-event-vector.h"
76 #include "xml/sp-css-attr.h"
77
78 #include "display/curve.h"
79 #include "message-stack.h"
80 #include "path-chemistry.h"
81 #include "ui/icon-loader.h"
82 #include "ui/tools-switch.h"
83 #include "ui/tools/node-tool.h"
84 #include "ui/tools/pen-tool.h"
85
86 #include "object/sp-defs.h"
87 #include "object/sp-root.h"
88 #include "object/sp-shape.h"
89
90 #include <cstdio>
91 #include <cstring>
92 #include <pangomm/layout.h>
93 #include <gtkmm/expander.h>
94
95 namespace Inkscape {
96
97 namespace LivePathEffect {
98
99 const EnumEffectData<EffectType> LPETypeData[] = {
100 // {constant defined in effect-enum.h, N_("name of your effect"), "name of your effect in SVG"}
101 /* 0.46 */
102 {
103 BEND_PATH,
104 N_("Bend") ,//label
105 "bend_path" ,//key
106 "bend-path" ,//icon
107 "Bend" ,//untranslated name
108 N_("Bend an object along the curvature of another path") ,//description
109 true ,//on_path
110 true ,//on_shape
111 true ,//on_group
112 false ,//on_image
113 false ,//on_text
114 false ,//experimental
115 },
116 {
117 GEARS,
118 N_("Gears") ,//label
119 "gears" ,//key
120 "gears" ,//icon
121 "Gears" ,//untranslated name
122 N_("Create interlocking, configurable gears based on the nodes of a path") ,//description
123 true ,//on_path
124 true ,//on_shape
125 true ,//on_group
126 false ,//on_image
127 false ,//on_text
128 false ,//experimental
129 },
130 {
131 PATTERN_ALONG_PATH,
132 N_("Pattern Along Path") ,//label
133 "skeletal" ,//key
134 "skeletal" ,//icon
135 "Pattern Along Path" ,//untranslated name
136 N_("Place one or more copies of another path along the path") ,//description
137 true ,//on_path
138 true ,//on_shape
139 true ,//on_group
140 false ,//on_image
141 false ,//on_text
142 false ,//experimental
143 }, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
144 {
145 CURVE_STITCH,
146 N_("Stitch Sub-Paths") ,//label
147 "curvestitching" ,//key
148 "curvestitching" ,//icon
149 "Stitch Sub-Paths" ,//untranslated name
150 N_("Draw perpendicular lines between subpaths of a path, like rungs of a ladder") ,//description
151 true ,//on_path
152 false ,//on_shape
153 true ,//on_group
154 false ,//on_image
155 false ,//on_text
156 false ,//experimental
157 },
158 /* 0.47 */
159 {
160 VONKOCH,
161 N_("VonKoch") ,//label
162 "vonkoch" ,//key
163 "vonkoch" ,//icon
164 "VonKoch" ,//untranslated name
165 N_("Create VonKoch fractal") ,//description
166 true ,//on_path
167 true ,//on_shape
168 true ,//on_group
169 false ,//on_image
170 false ,//on_text
171 false ,//experimental
172 },
173 {
174 KNOT,
175 N_("Knot") ,//label
176 "knot" ,//key
177 "knot" ,//icon
178 "Knot" ,//untranslated name
179 N_("Create gaps in self-intersections, as in Celtic knots") ,//description
180 true ,//on_path
181 true ,//on_shape
182 true ,//on_group
183 false ,//on_image
184 false ,//on_text
185 false ,//experimental
186 },
187 {
188 CONSTRUCT_GRID,
189 N_("Construct grid") ,//label
190 "construct_grid" ,//key
191 "construct-grid" ,//icon
192 "Construct grid" ,//untranslated name
193 N_("Create a (perspective) grid from a 3-node path") ,//description
194 true ,//on_path
195 true ,//on_shape
196 true ,//on_group
197 false ,//on_image
198 false ,//on_text
199 false ,//experimental
200 },
201 {
202 SPIRO,
203 N_("Spiro spline") ,//label
204 "spiro" ,//key
205 "spiro" ,//icon
206 "Spiro spline" ,//untranslated name
207 N_("Make the path curl like wire, using Spiro B-Splines. This effect is usually used directly on the canvas with the Spiro mode of the drawing tools.") ,//description
208 true ,//on_path
209 false ,//on_shape
210 false ,//on_group
211 false ,//on_image
212 false ,//on_text
213 false ,//experimental
214 },
215 {
216 ENVELOPE,
217 N_("Envelope Deformation") ,//label
218 "envelope" ,//key
219 "envelope" ,//icon
220 "Envelope Deformation" ,//untranslated name
221 N_("Adjust the shape of an object by transforming paths on its four sides") ,//description
222 true ,//on_path
223 true ,//on_shape
224 true ,//on_group
225 false ,//on_image
226 false ,//on_text
227 false ,//experimental
228 },
229 {
230 INTERPOLATE,
231 N_("Interpolate Sub-Paths") ,//label
232 "interpolate" ,//key
233 "interpolate" ,//icon
234 "Interpolate Sub-Paths" ,//untranslated name
235 N_("Create a stepwise transition between the 2 subpaths of a path") ,//description
236 true ,//on_path
237 false ,//on_shape
238 false ,//on_group
239 false ,//on_image
240 false ,//on_text
241 false ,//experimental
242 },
243 {
244 ROUGH_HATCHES,
245 N_("Hatches (rough)") ,//label
246 "rough_hatches" ,//key
247 "rough-hatches" ,//icon
248 "Hatches (rough)" ,//untranslated name
249 N_("Fill the object with adjustable hatching") ,//description
250 true ,//on_path
251 true ,//on_shape
252 true ,//on_group
253 false ,//on_image
254 false ,//on_text
255 false ,//experimental
256 },
257 {
258 SKETCH,
259 N_("Sketch") ,//label
260 "sketch" ,//key
261 "sketch" ,//icon
262 "Sketch" ,//untranslated name
263 N_("Draw multiple short strokes along the path, as in a pencil sketch") ,//description
264 true ,//on_path
265 true ,//on_shape
266 true ,//on_group
267 false ,//on_image
268 false ,//on_text
269 false ,//experimental
270 },
271 {
272 RULER,
273 N_("Ruler") ,//label
274 "ruler" ,//key
275 "ruler" ,//icon
276 "Ruler" ,//untranslated name
277 N_("Add ruler marks to the object in adjustable intervals, using the object's stroke style.") ,//description
278 true ,//on_path
279 true ,//on_shape
280 true ,//on_group
281 false ,//on_image
282 false ,//on_text
283 false ,//experimental
284 },
285 /* 0.91 */
286 {
287 POWERSTROKE,
288 N_("Power stroke") ,//label
289 "powerstroke" ,//key
290 "powerstroke" ,//icon
291 "Power stroke" ,//untranslated name
292 N_("Create calligraphic strokes and control their variable width and curvature. This effect can also be used directly on the canvas with a pressure sensitive stylus and the Pencil tool.") ,//description
293 true ,//on_path
294 true ,//on_shape
295 false ,//on_group
296 false ,//on_image
297 false ,//on_text
298 false ,//experimental
299 },
300 {
301 CLONE_ORIGINAL,
302 N_("Clone original") ,//label
303 "clone_original" ,//key
304 "clone-original" ,//icon
305 "Clone original" ,//untranslated name
306 N_("Let an object take on the shape, fill, stroke and/or other attributes of another object.") ,//description
307 true ,//on_path
308 true ,//on_shape
309 true ,//on_group
310 false ,//on_image
311 false ,//on_text
312 false ,//experimental
313 },
314 /* 0.92 */
315 {
316 SIMPLIFY,
317 N_("Simplify") ,//label
318 "simplify" ,//key
319 "simplify" ,//icon
320 "Simplify" ,//untranslated name
321 N_("Smoothen and simplify a object. This effect is also available in the Pencil tool's tool controls.") ,//description
322 true ,//on_path
323 true ,//on_shape
324 true ,//on_group
325 false ,//on_image
326 false ,//on_text
327 false ,//experimental
328 },
329 {
330 LATTICE2,
331 N_("Lattice Deformation 2") ,//label
332 "lattice2" ,//key
333 "lattice2" ,//icon
334 "Lattice Deformation 2" ,//untranslated name
335 N_("Warp an object's shape based on a 5x5 grid") ,//description
336 true ,//on_path
337 true ,//on_shape
338 true ,//on_group
339 false ,//on_image
340 false ,//on_text
341 false ,//experimental
342 },
343 {
344 PERSPECTIVE_ENVELOPE,
345 N_("Perspective/Envelope") ,//label
346 "perspective-envelope" ,//key wrong key with "-" retain because historic
347 "perspective-envelope" ,//icon
348 "Perspective/Envelope" ,//untranslated name
349 N_("Transform the object to fit into a shape with four corners, either by stretching it or creating the illusion of a 3D-perspective") ,//description
350 true ,//on_path
351 true ,//on_shape
352 true ,//on_group
353 false ,//on_image
354 false ,//on_text
355 false ,//experimental
356 },
357 {
358 INTERPOLATE_POINTS,
359 N_("Interpolate points") ,//label
360 "interpolate_points" ,//key
361 "interpolate-points" ,//icon
362 "Interpolate points" ,//untranslated name
363 N_("Connect the nodes of the object (e.g. corresponding to data points) by different types of lines.") ,//description
364 true ,//on_path
365 true ,//on_shape
366 true ,//on_group
367 false ,//on_image
368 false ,//on_text
369 false ,//experimental
370 },
371 {
372 TRANSFORM_2PTS,
373 N_("Transform by 2 points") ,//label
374 "transform_2pts" ,//key
375 "transform-2pts" ,//icon
376 "Transform by 2 points" ,//untranslated name
377 N_("Scale, stretch and rotate an object by two handles") ,//description
378 true ,//on_path
379 true ,//on_shape
380 true ,//on_group
381 false ,//on_image
382 false ,//on_text
383 false ,//experimental
384 },
385 {
386 SHOW_HANDLES,
387 N_("Show handles") ,//label
388 "show_handles" ,//key
389 "show-handles" ,//icon
390 "Show handles" ,//untranslated name
391 N_("Draw the handles and nodes of objects (replaces the original styling with a black stroke)") ,//description
392 true ,//on_path
393 true ,//on_shape
394 true ,//on_group
395 false ,//on_image
396 false ,//on_text
397 false ,//experimental
398 },
399 {
400 ROUGHEN,
401 N_("Roughen") ,//label
402 "roughen" ,//key
403 "roughen" ,//icon
404 "Roughen" ,//untranslated name
405 N_("Roughen an object by adding and randomly shifting new nodes") ,//description
406 true ,//on_path
407 true ,//on_shape
408 true ,//on_group
409 false ,//on_image
410 false ,//on_text
411 false ,//experimental
412 },
413 {
414 BSPLINE,
415 N_("BSpline") ,//label
416 "bspline" ,//key
417 "bspline" ,//icon
418 "BSpline" ,//untranslated name
419 N_("Create a BSpline that molds into the path's corners. This effect is usually used directly on the canvas with the BSpline mode of the drawing tools.") ,//description
420 true ,//on_path
421 false ,//on_shape
422 false ,//on_group
423 false ,//on_image
424 false ,//on_text
425 false ,//experimental
426 },
427 {
428 JOIN_TYPE,
429 N_("Join type") ,//label
430 "join_type" ,//key
431 "join-type" ,//icon
432 "Join type" ,//untranslated name
433 N_("Select among various join types for a object's corner nodes (mitre, rounded, extrapolated arc, ...)") ,//description
434 true ,//on_path
435 true ,//on_shape
436 true ,//on_group
437 false ,//on_image
438 false ,//on_text
439 false ,//experimental
440 },
441 {
442 TAPER_STROKE,
443 N_("Taper stroke") ,//label
444 "taper_stroke" ,//key
445 "taper-stroke" ,//icon
446 "Taper stroke" ,//untranslated name
447 N_("Let the path's ends narrow down to a tip") ,//description
448 true ,//on_path
449 true ,//on_shape
450 false ,//on_group
451 false ,//on_image
452 false ,//on_text
453 false ,//experimental
454 },
455 {
456 MIRROR_SYMMETRY,
457 N_("Mirror symmetry") ,//label
458 "mirror_symmetry" ,//key
459 "mirror-symmetry" ,//icon
460 "Mirror symmetry" ,//untranslated name
461 N_("Mirror an object along a movable axis, or around the page center. The mirrored copy can be styled independently.") ,//description
462 true ,//on_path
463 true ,//on_shape
464 true ,//on_group
465 false ,//on_image
466 false ,//on_text
467 false ,//experimental
468 },
469 {
470 COPY_ROTATE,
471 N_("Rotate copies") ,//label
472 "copy_rotate" ,//key
473 "copy-rotate" ,//icon
474 "Rotate copies" ,//untranslated name
475 N_("Create multiple rotated copies of an object, as in a kaleidoscope. The copies can be styled independently.") ,//description
476 true ,//on_path
477 true ,//on_shape
478 true ,//on_group
479 false ,//on_image
480 false ,//on_text
481 false ,//experimental
482 },
483 /* Ponyscape -> Inkscape 0.92*/
484 {
485 ATTACH_PATH,
486 N_("Attach path") ,//label
487 "attach_path" ,//key
488 "attach-path" ,//icon
489 "Attach path" ,//untranslated name
490 N_("Glue the current path's ends to a specific position on one or two other paths") ,//description
491 true ,//on_path
492 true ,//on_shape
493 true ,//on_group
494 false ,//on_image
495 false ,//on_text
496 false ,//experimental
497 },
498 {
499 FILL_BETWEEN_STROKES,
500 N_("Fill between strokes") ,//label
501 "fill_between_strokes" ,//key
502 "fill-between-strokes" ,//icon
503 "Fill between strokes" ,//untranslated name
504 N_("Turn the path into a fill between two other open paths (e.g. between two paths with PowerStroke applied to them)") ,//description
505 true ,//on_path
506 true ,//on_shape
507 true ,//on_group
508 false ,//on_image
509 false ,//on_text
510 false ,//experimental
511 },
512 {
513 FILL_BETWEEN_MANY,
514 N_("Fill between many") ,//label
515 "fill_between_many" ,//key
516 "fill-between-many" ,//icon
517 "Fill between many" ,//untranslated name
518 N_("Turn the path into a fill between multiple other open paths (e.g. between paths with PowerStroke applied to them)") ,//description
519 true ,//on_path
520 true ,//on_shape
521 true ,//on_group
522 false ,//on_image
523 false ,//on_text
524 false ,//experimental
525 },
526 {
527 ELLIPSE_5PTS,
528 N_("Ellipse by 5 points") ,//label
529 "ellipse_5pts" ,//key
530 "ellipse-5pts" ,//icon
531 "Ellipse by 5 points" ,//untranslated name
532 N_("Create an ellipse from 5 nodes on its circumference") ,//description
533 true ,//on_path
534 true ,//on_shape
535 false ,//on_group
536 false ,//on_image
537 false ,//on_text
538 false ,//experimental
539 },
540 {
541 BOUNDING_BOX,
542 N_("Bounding Box") ,//label
543 "bounding_box" ,//key
544 "bounding-box" ,//icon
545 "Bounding Box" ,//untranslated name
546 N_("Turn the path into a bounding box that entirely encompasses another path") ,//description
547 true ,//on_path
548 true ,//on_shape
549 true ,//on_group
550 false ,//on_image
551 false ,//on_text
552 false ,//experimental
553 },
554 /* 1.0 */
555 {
556 MEASURE_SEGMENTS,
557 N_("Measure Segments") ,//label
558 "measure_segments" ,//key
559 "measure-segments" ,//icon
560 "Measure Segments" ,//untranslated name
561 N_("Add dimensioning for distances between nodes, optionally with projection and many other configuration options") ,//description
562 true ,//on_path
563 true ,//on_shape
564 false ,//on_group
565 false ,//on_image
566 false ,//on_text
567 false ,//experimental
568 },
569 {
570 FILLET_CHAMFER,
571 N_("Corners (Fillet/Chamfer)") ,//label
572 "fillet_chamfer" ,//key
573 "fillet-chamfer" ,//icon
574 "Corners (Fillet/Chamfer)" ,//untranslated name
575 N_("Adjust the shape of a path's corners, rounding them to a specified radius, or cutting them off") ,//description
576 true ,//on_path
577 true ,//on_shape
578 false ,//on_group
579 false ,//on_image
580 false ,//on_text
581 false ,//experimental
582 },
583 {
584 BOOL_OP,
585 N_("Boolean operation") ,//label
586 "bool_op" ,//key
587 "bool-op" ,//icon
588 "Boolean operation" ,//untranslated name
589 N_("Cut, union, subtract, intersect and divide a path non-destructively with another path") ,//description
590 true ,//on_path
591 true ,//on_shape
592 true ,//on_group
593 false ,//on_image
594 false ,//on_text
595 false ,//experimental
596 },
597 {
598 POWERCLIP,
599 N_("Power clip") ,//label
600 "powerclip" ,//key
601 "powerclip" ,//icon
602 "Power clip" ,//untranslated name
603 N_("Invert, hide or flatten a clip (apply like a Boolean operation)") ,//description
604 true ,//on_path
605 true ,//on_shape
606 true ,//on_group
607 false ,//on_image
608 false ,//on_text
609 false ,//experimental
610 },
611 {
612 POWERMASK,
613 N_("Power mask") ,//label
614 "powermask" ,//key
615 "powermask" ,//icon
616 "Power mask" ,//untranslated name
617 N_("Invert or hide a mask, or use its negative") ,//description
618 true ,//on_path
619 true ,//on_shape
620 true ,//on_group
621 false ,//on_image
622 false ,//on_text
623 false ,//experimental
624 },
625 {
626 PTS2ELLIPSE,
627 N_("Ellipse from points") ,//label
628 "pts2ellipse" ,//key
629 "pts2ellipse" ,//icon
630 "Ellipse from points" ,//untranslated name
631 N_("Draw a circle, ellipse, arc or slice based on the nodes of a path") ,//description
632 true ,//on_path
633 true ,//on_shape
634 true ,//on_group
635 false ,//on_image
636 false ,//on_text
637 false ,//experimental
638 },
639 {
640 OFFSET,
641 N_("Offset") ,//label
642 "offset" ,//key
643 "offset" ,//icon
644 "Offset" ,//untranslated name
645 N_("Offset the path, optionally keeping cusp corners cusp") ,//description
646 true ,//on_path
647 true ,//on_shape
648 true ,//on_group
649 false ,//on_image
650 false ,//on_text
651 false ,//experimental
652 },
653 {
654 DASHED_STROKE,
655 N_("Dashed Stroke") ,//label
656 "dashed_stroke" ,//key
657 "dashed-stroke" ,//icon
658 "Dashed Stroke" ,//untranslated name
659 N_("Add a dashed stroke whose dashes end exactly on a node, optionally with the same number of dashes per path segment") ,//description
660 true ,//on_path
661 true ,//on_shape
662 true ,//on_group
663 false ,//on_image
664 false ,//on_text
665 false ,//experimental
666 },
667 {
668 ANGLE_BISECTOR,
669 N_("Angle bisector") ,//label
670 "angle_bisector" ,//key
671 "experimental" ,//icon
672 "Angle bisector" ,//untranslated name
673 N_("Draw a line that halves the angle between the first three nodes of the path") ,//description
674 true ,//on_path
675 true ,//on_shape
676 true ,//on_group
677 false ,//on_image
678 false ,//on_text
679 true ,//experimental
680 },
681 {
682 CIRCLE_WITH_RADIUS,
683 N_("Circle (by center and radius)") ,//label
684 "circle_with_radius" ,//key
685 "experimental" ,//icon
686 "Circle (by center and radius)" ,//untranslated name
687 N_("Draw a circle, where the first node of the path is the center, and the last determines its radius") ,//description
688 true ,//on_path
689 true ,//on_shape
690 true ,//on_group
691 false ,//on_image
692 false ,//on_text
693 true ,//experimental
694 },
695 {
696 CIRCLE_3PTS,
697 N_("Circle by 3 points") ,//label
698 "circle_3pts" ,//key
699 "experimental" ,//icon
700 "Circle by 3 points" ,//untranslated name
701 N_("Draw a circle whose circumference passes through the first three nodes of the path") ,//description
702 true ,//on_path
703 true ,//on_shape
704 true ,//on_group
705 false ,//on_image
706 false ,//on_text
707 true ,//experimental
708 },
709 {
710 EXTRUDE,
711 N_("Extrude") ,//label
712 "extrude" ,//key
713 "experimental" ,//icon
714 "Extrude" ,//untranslated name
715 N_("Extrude the path, creating a face for each path segment") ,//description
716 true ,//on_path
717 true ,//on_shape
718 true ,//on_group
719 false ,//on_image
720 false ,//on_text
721 true ,//experimental
722 },
723 {
724 LINE_SEGMENT,
725 N_("Line Segment") ,//label
726 "line_segment" ,//key
727 "experimental" ,//icon
728 "Line Segment" ,//untranslated name
729 N_("Draw a straight line that connects the first and last node of a path") ,//description
730 true ,//on_path
731 true ,//on_shape
732 true ,//on_group
733 false ,//on_image
734 false ,//on_text
735 true ,//experimental
736 },
737 {
738 PARALLEL,
739 N_("Parallel") ,//label
740 "parallel" ,//key
741 "experimental" ,//icon
742 "Parallel" ,//untranslated name
743 N_("Create a draggable line that will always be parallel to a two-node path") ,//description
744 true ,//on_path
745 true ,//on_shape
746 true ,//on_group
747 false ,//on_image
748 false ,//on_text
749 true ,//experimental
750 },
751 {
752 PERP_BISECTOR,
753 N_("Perpendicular bisector") ,//label
754 "perp_bisector" ,//key
755 "experimental" ,//icon
756 "Perpendicular bisector" ,//untranslated name
757 N_("Draw a perpendicular line in the middle of the (imaginary) line that connects the start and end nodes") ,//description
758 true ,//on_path
759 true ,//on_shape
760 true ,//on_group
761 false ,//on_image
762 false ,//on_text
763 true ,//experimental
764 },
765 {
766 TANGENT_TO_CURVE,
767 N_("Tangent to curve") ,//label
768 "tangent_to_curve" ,//key
769 "experimental" ,//icon
770 "Tangent to curve" ,//untranslated name
771 N_("Draw a tangent with variable length and additional angle that can be moved along the path") ,//description
772 true ,//on_path
773 true ,//on_shape
774 true ,//on_group
775 false ,//on_image
776 false ,//on_text
777 true ,//experimental
778 },
779 /* 1.1 */
780 {
781 SLICE,
782 NC_("path effect", "Slice") ,//label
783 "slice" ,//key
784 "slice" ,//icon
785 "Slice" ,//untranslated name
786 N_("Slices the item into parts. It can also be applied multiple times.") ,//description
787 true ,//on_path
788 true ,//on_shape
789 true ,//on_group
790 false ,//on_image
791 false ,//on_text
792 false ,//experimental
793 },
794 #ifdef LPE_ENABLE_TEST_EFFECTS
795 {
796 DOEFFECTSTACK_TEST,
797 N_("doEffect stack test") ,//label
798 "doeffectstacktest" ,//key
799 "experimental" ,//icon
800 "doEffect stack test" ,//untranslated name
801 N_("Test LPE") ,//description
802 true ,//on_path
803 true ,//on_shape
804 true ,//on_group
805 false ,//on_image
806 false ,//on_text
807 true ,//experimental
808 },
809 {
810 DYNASTROKE,
811 N_("Dynamic stroke") ,//label
812 "dynastroke" ,//key
813 "experimental" ,//icon
814 "Dynamic stroke" ,//untranslated name
815 N_("Create calligraphic strokes with variably shaped ends, making use of a parameter for the brush angle") ,//description
816 true ,//on_path
817 true ,//on_shape
818 true ,//on_group
819 false ,//on_image
820 false ,//on_text
821 true ,//experimental
822 },
823 {
824 LATTICE,
825 N_("Lattice Deformation") ,//label
826 "lattice" ,//key
827 "experimental" ,//icon
828 "Lattice Deformation" ,//untranslated name
829 N_("Deform an object using a 4x4 grid") ,//description
830 true ,//on_path
831 true ,//on_shape
832 true ,//on_group
833 false ,//on_image
834 false ,//on_text
835 true ,//experimental
836 },
837 {
838 PATH_LENGTH,
839 N_("Path length") ,//label
840 "path_length" ,//key
841 "experimental" ,//icon
842 "Path length" ,//untranslated name
843 N_("Display the total length of a (curved) path") ,//description
844 true ,//on_path
845 true ,//on_shape
846 true ,//on_group
847 false ,//on_image
848 false ,//on_text
849 true ,//experimental
850 },
851 {
852 RECURSIVE_SKELETON,
853 N_("Recursive skeleton") ,//label
854 "recursive_skeleton" ,//key
855 "experimental" ,//icon
856 "Recursive skeleton" ,//untranslated name
857 N_("Draw a path recursively") ,//description
858 true ,//on_path
859 true ,//on_shape
860 true ,//on_group
861 false ,//on_image
862 false ,//on_text
863 true ,//experimental
864 },
865 {
866 TEXT_LABEL,
867 N_("Text label") ,//label
868 "text_label" ,//key
869 "experimental" ,//icon
870 "Text label" ,//untranslated name
871 N_("Add a label for the object") ,//description
872 true ,//on_path
873 true ,//on_shape
874 true ,//on_group
875 false ,//on_image
876 false ,//on_text
877 true ,//experimental
878 },
879 {
880 EMBRODERY_STITCH,
881 N_("Embroidery stitch") ,//label
882 "embrodery_stitch" ,//key
883 "embrodery-stitch" ,//icon
884 "Embroidery stitch" ,//untranslated name
885 N_("Embroidery stitch") ,//description
886 true ,//on_path
887 true ,//on_shape
888 true ,//on_group
889 false ,//on_image
890 false ,//on_text
891 false ,//experimental
892 },
893 #endif
894
895 };
896
897 const EnumEffectDataConverter<EffectType> LPETypeConverter(LPETypeData, sizeof(LPETypeData) / sizeof(*LPETypeData));
898
899 int
acceptsNumClicks(EffectType type)900 Effect::acceptsNumClicks(EffectType type) {
901 switch (type) {
902 case INVALID_LPE: return -1; // in case we want to distinguish between invalid LPE and valid ones that expect zero clicks
903 case ANGLE_BISECTOR: return 3;
904 case CIRCLE_3PTS: return 3;
905 case CIRCLE_WITH_RADIUS: return 2;
906 case LINE_SEGMENT: return 2;
907 case PERP_BISECTOR: return 2;
908 default: return 0;
909 }
910 }
911
912 Effect*
New(EffectType lpenr,LivePathEffectObject * lpeobj)913 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
914 {
915 Effect* neweffect = nullptr;
916 switch (lpenr) {
917 case EMBRODERY_STITCH:
918 neweffect = static_cast<Effect*> ( new LPEEmbroderyStitch(lpeobj) );
919 break;
920 case BOOL_OP:
921 neweffect = static_cast<Effect*> ( new LPEBool(lpeobj) );
922 break;
923 case PATTERN_ALONG_PATH:
924 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
925 break;
926 case BEND_PATH:
927 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
928 break;
929 case SKETCH:
930 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
931 break;
932 case ROUGH_HATCHES:
933 neweffect = static_cast<Effect*> ( new LPERoughHatches(lpeobj) );
934 break;
935 case VONKOCH:
936 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
937 break;
938 case KNOT:
939 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
940 break;
941 case GEARS:
942 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
943 break;
944 case CURVE_STITCH:
945 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
946 break;
947 case LATTICE:
948 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
949 break;
950 case ENVELOPE:
951 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
952 break;
953 case CIRCLE_WITH_RADIUS:
954 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
955 break;
956 case SPIRO:
957 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
958 break;
959 case CONSTRUCT_GRID:
960 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
961 break;
962 case PERP_BISECTOR:
963 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
964 break;
965 case TANGENT_TO_CURVE:
966 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
967 break;
968 case MIRROR_SYMMETRY:
969 neweffect = static_cast<Effect*> ( new LPEMirrorSymmetry(lpeobj) );
970 break;
971 case CIRCLE_3PTS:
972 neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
973 break;
974 case ANGLE_BISECTOR:
975 neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
976 break;
977 case PARALLEL:
978 neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
979 break;
980 case COPY_ROTATE:
981 neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
982 break;
983 case OFFSET:
984 neweffect = static_cast<Effect*> ( new LPEOffset(lpeobj) );
985 break;
986 case RULER:
987 neweffect = static_cast<Effect*> ( new LPERuler(lpeobj) );
988 break;
989 case INTERPOLATE:
990 neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) );
991 break;
992 case INTERPOLATE_POINTS:
993 neweffect = static_cast<Effect*> ( new LPEInterpolatePoints(lpeobj) );
994 break;
995 case TEXT_LABEL:
996 neweffect = static_cast<Effect*> ( new LPETextLabel(lpeobj) );
997 break;
998 case PATH_LENGTH:
999 neweffect = static_cast<Effect*> ( new LPEPathLength(lpeobj) );
1000 break;
1001 case LINE_SEGMENT:
1002 neweffect = static_cast<Effect*> ( new LPELineSegment(lpeobj) );
1003 break;
1004 case DOEFFECTSTACK_TEST:
1005 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
1006 break;
1007 case BSPLINE:
1008 neweffect = static_cast<Effect*> ( new LPEBSpline(lpeobj) );
1009 break;
1010 case DYNASTROKE:
1011 neweffect = static_cast<Effect*> ( new LPEDynastroke(lpeobj) );
1012 break;
1013 case RECURSIVE_SKELETON:
1014 neweffect = static_cast<Effect*> ( new LPERecursiveSkeleton(lpeobj) );
1015 break;
1016 case EXTRUDE:
1017 neweffect = static_cast<Effect*> ( new LPEExtrude(lpeobj) );
1018 break;
1019 case POWERSTROKE:
1020 neweffect = static_cast<Effect*> ( new LPEPowerStroke(lpeobj) );
1021 break;
1022 case CLONE_ORIGINAL:
1023 neweffect = static_cast<Effect*> ( new LPECloneOriginal(lpeobj) );
1024 break;
1025 case ATTACH_PATH:
1026 neweffect = static_cast<Effect*> ( new LPEAttachPath(lpeobj) );
1027 break;
1028 case FILL_BETWEEN_STROKES:
1029 neweffect = static_cast<Effect*> ( new LPEFillBetweenStrokes(lpeobj) );
1030 break;
1031 case FILL_BETWEEN_MANY:
1032 neweffect = static_cast<Effect*> ( new LPEFillBetweenMany(lpeobj) );
1033 break;
1034 case ELLIPSE_5PTS:
1035 neweffect = static_cast<Effect*> ( new LPEEllipse5Pts(lpeobj) );
1036 break;
1037 case BOUNDING_BOX:
1038 neweffect = static_cast<Effect*> ( new LPEBoundingBox(lpeobj) );
1039 break;
1040 case JOIN_TYPE:
1041 neweffect = static_cast<Effect*> ( new LPEJoinType(lpeobj) );
1042 break;
1043 case TAPER_STROKE:
1044 neweffect = static_cast<Effect*> ( new LPETaperStroke(lpeobj) );
1045 break;
1046 case SIMPLIFY:
1047 neweffect = static_cast<Effect*> ( new LPESimplify(lpeobj) );
1048 break;
1049 case LATTICE2:
1050 neweffect = static_cast<Effect*> ( new LPELattice2(lpeobj) );
1051 break;
1052 case PERSPECTIVE_ENVELOPE:
1053 neweffect = static_cast<Effect*> ( new LPEPerspectiveEnvelope(lpeobj) );
1054 break;
1055 case FILLET_CHAMFER:
1056 neweffect = static_cast<Effect*> ( new LPEFilletChamfer(lpeobj) );
1057 break;
1058 case POWERCLIP:
1059 neweffect = static_cast<Effect*> ( new LPEPowerClip(lpeobj) );
1060 break;
1061 case POWERMASK:
1062 neweffect = static_cast<Effect*> ( new LPEPowerMask(lpeobj) );
1063 break;
1064 case ROUGHEN:
1065 neweffect = static_cast<Effect*> ( new LPERoughen(lpeobj) );
1066 break;
1067 case SHOW_HANDLES:
1068 neweffect = static_cast<Effect*> ( new LPEShowHandles(lpeobj) );
1069 break;
1070 case TRANSFORM_2PTS:
1071 neweffect = static_cast<Effect*> ( new LPETransform2Pts(lpeobj) );
1072 break;
1073 case MEASURE_SEGMENTS:
1074 neweffect = static_cast<Effect*> ( new LPEMeasureSegments(lpeobj) );
1075 break;
1076 case PTS2ELLIPSE:
1077 neweffect = static_cast<Effect*> ( new LPEPts2Ellipse(lpeobj) );
1078 break;
1079 case DASHED_STROKE:
1080 neweffect = static_cast<Effect *>(new LPEDashedStroke(lpeobj));
1081 break;
1082 case SLICE:
1083 neweffect = static_cast<Effect *>(new LPESlice(lpeobj));
1084 break;
1085 default:
1086 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
1087 neweffect = nullptr;
1088 break;
1089 }
1090
1091 if (neweffect) {
1092 neweffect->readallParameters(lpeobj->getRepr());
1093 }
1094
1095 return neweffect;
1096 }
1097
createAndApply(const char * name,SPDocument * doc,SPItem * item)1098 void Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
1099 {
1100 // Path effect definition
1101 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
1102 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
1103 repr->setAttribute("effect", name);
1104
1105 doc->getDefs()->getRepr()->addChild(repr, nullptr); // adds to <defs> and assigns the 'id' attribute
1106 const gchar * repr_id = repr->attribute("id");
1107 Inkscape::GC::release(repr);
1108
1109 gchar *href = g_strdup_printf("#%s", repr_id);
1110 SP_LPE_ITEM(item)->addPathEffect(href, true);
1111 g_free(href);
1112 }
1113
1114 void
createAndApply(EffectType type,SPDocument * doc,SPItem * item)1115 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
1116 {
1117 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
1118 }
1119
Effect(LivePathEffectObject * lpeobject)1120 Effect::Effect(LivePathEffectObject *lpeobject)
1121 : apply_to_clippath_and_mask(false),
1122 _provides_knotholder_entities(false),
1123 oncanvasedit_it(0),
1124 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
1125 lpeversion(_("Version"), _("LPE version"), "lpeversion", &wr, this, "0", true),
1126 show_orig_path(false),
1127 keep_paths(false),
1128 is_load(true),
1129 on_remove_all(false),
1130 lpeobj(lpeobject),
1131 concatenate_before_pwd2(false),
1132 sp_lpe_item(nullptr),
1133 current_zoom(0),
1134 refresh_widgets(false),
1135 current_shape(nullptr),
1136 provides_own_flash_paths(true), // is automatically set to false if providesOwnFlashPaths() is not overridden
1137 defaultsopen(false),
1138 is_ready(false),
1139 is_applied(false)
1140 {
1141 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
1142 registerParameter( dynamic_cast<Parameter *>(&lpeversion) );
1143 is_visible.widget_is_visible = false;
1144 }
1145
1146 Effect::~Effect() = default;
1147
1148 Glib::ustring
getName() const1149 Effect::getName() const
1150 {
1151 if (lpeobj->effecttype_set && LPETypeConverter.is_valid_id(lpeobj->effecttype) )
1152 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
1153 else
1154 return Glib::ustring( _("No effect") );
1155 }
1156
1157 EffectType
effectType() const1158 Effect::effectType() const {
1159 return lpeobj->effecttype;
1160 }
1161
1162 std::vector<SPLPEItem *>
getCurrrentLPEItems() const1163 Effect::getCurrrentLPEItems() const {
1164 std::vector<SPLPEItem *> result;
1165 auto hreflist = getLPEObj()->hrefList;
1166 for (auto item : hreflist) {
1167 SPLPEItem * lpeitem = dynamic_cast<SPLPEItem *>(item);
1168 if (lpeitem) {
1169 result.push_back(lpeitem);
1170 }
1171 }
1172 return result;
1173 }
1174
1175 /**
1176 * Is performed a single time when the effect is freshly applied to a path
1177 */
1178 void
doOnApply(SPLPEItem const *)1179 Effect::doOnApply (SPLPEItem const*/*lpeitem*/)
1180 {
1181 }
1182
1183 void
setCurrentZoom(double cZ)1184 Effect::setCurrentZoom(double cZ)
1185 {
1186 current_zoom = cZ;
1187 }
1188
1189 /**
1190 * Overrided function to apply transforms for example to powerstrole, jointtype or tapperstroke
1191 */
transform_multiply(Geom::Affine const & postmul,bool)1192 void Effect::transform_multiply(Geom::Affine const &postmul, bool /*set*/) {}
1193
1194 /**
1195 * @param lpeitem The item being transformed
1196 *
1197 * @pre effect is referenced by lpeitem
1198 *
1199 * FIXME Probably only makes sense if this effect is referenced by exactly one
1200 * item (`this->lpeobj->hrefList` contains exactly one element)?
1201 */
transform_multiply(Geom::Affine const & postmul,SPLPEItem * lpeitem)1202 void Effect::transform_multiply(Geom::Affine const &postmul, SPLPEItem *lpeitem)
1203 {
1204 assert("pre: effect is referenced by lpeitem" &&
1205 std::any_of(lpeobj->hrefList.begin(), lpeobj->hrefList.end(),
1206 [lpeitem](SPObject *obj) { return lpeitem == dynamic_cast<SPLPEItem *>(obj); }));
1207
1208 // FIXME Is there a way to eliminate the raw Effect::sp_lpe_item pointer?
1209 sp_lpe_item = lpeitem;
1210
1211 transform_multiply(postmul, false);
1212 }
1213
1214 void
setSelectedNodePoints(std::vector<Geom::Point> sNP)1215 Effect::setSelectedNodePoints(std::vector<Geom::Point> sNP)
1216 {
1217 selectedNodesPoints = sNP;
1218 }
1219
1220 /**
1221 * The lpe is on clipboard
1222 */
isOnClipboard()1223 bool Effect::isOnClipboard()
1224 {
1225 SPDocument *document = getSPDoc();
1226 if (!document) {
1227 return false;
1228 }
1229 Inkscape::XML::Node *root = document->getReprRoot();
1230 Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
1231 return clipnode != nullptr;
1232 }
1233
1234 bool
isNodePointSelected(Geom::Point const & nodePoint) const1235 Effect::isNodePointSelected(Geom::Point const &nodePoint) const
1236 {
1237 if (selectedNodesPoints.size() > 0) {
1238 using Geom::X;
1239 using Geom::Y;
1240 for (auto p : selectedNodesPoints) {
1241 Geom::Affine transformCoordinate = sp_lpe_item->i2dt_affine();
1242 Geom::Point p2(nodePoint[X],nodePoint[Y]);
1243 p2 *= transformCoordinate;
1244 if (Geom::are_near(p, p2, 0.01)) {
1245 return true;
1246 }
1247 }
1248 }
1249 return false;
1250 }
1251
1252 void
processObjects(LPEAction lpe_action)1253 Effect::processObjects(LPEAction lpe_action)
1254 {
1255 SPDocument *document = getSPDoc();
1256 if (!document) {
1257 return;
1258 }
1259 sp_lpe_item = dynamic_cast<SPLPEItem *>(*getLPEObj()->hrefList.begin());
1260 if (!document || !sp_lpe_item) {
1261 return;
1262 }
1263 sp_lpe_item_enable_path_effects(sp_lpe_item, false);
1264 for (auto id : items) {
1265 SPObject *elemref = nullptr;
1266 if ((elemref = document->getObjectById(id.c_str()))) {
1267 Inkscape::XML::Node * elemnode = elemref->getRepr();
1268 std::vector<SPItem*> item_list;
1269 item_list.push_back(SP_ITEM(elemref));
1270 std::vector<Inkscape::XML::Node*> item_to_select;
1271 std::vector<SPItem*> item_selected;
1272 SPCSSAttr *css;
1273 Glib::ustring css_str;
1274 SPItem *item = SP_ITEM(elemref);
1275 switch (lpe_action){
1276 case LPE_TO_OBJECTS:
1277 if (item->isHidden()) {
1278 item->deleteObject(true);
1279 } else {
1280 elemnode->removeAttribute("sodipodi:insensitive");
1281 if (!SP_IS_DEFS(SP_ITEM(elemref)->parent)) {
1282 SP_ITEM(elemref)->moveTo(SP_ITEM(sp_lpe_item), false);
1283 }
1284 }
1285 break;
1286
1287 case LPE_ERASE:
1288 item->deleteObject(true);
1289 break;
1290
1291 case LPE_VISIBILITY:
1292 css = sp_repr_css_attr_new();
1293 sp_repr_css_attr_add_from_string(css, elemref->getRepr()->attribute("style"));
1294 if (!this->isVisible()/* && std::strcmp(elemref->getId(),sp_lpe_item->getId()) != 0*/) {
1295 css->setAttribute("display", "none");
1296 } else {
1297 css->removeAttribute("display");
1298 }
1299 sp_repr_css_write_string(css,css_str);
1300 elemnode->setAttributeOrRemoveIfEmpty("style", css_str);
1301 break;
1302
1303 default:
1304 break;
1305 }
1306 }
1307 }
1308 if (lpe_action == LPE_ERASE || lpe_action == LPE_TO_OBJECTS) {
1309 items.clear();
1310 }
1311 sp_lpe_item_enable_path_effects(sp_lpe_item, true);
1312 }
1313
1314 /**
1315 * Is performed each time before the effect is updated.
1316 */
1317 void
doBeforeEffect(SPLPEItem const *)1318 Effect::doBeforeEffect (SPLPEItem const*/*lpeitem*/)
1319 {
1320 //Do nothing for simple effects
1321 }
1322 /**
1323 * Is performed at the end of the LPE only one time per "lpeitem"
1324 * in paths/shapes is called in middle of the effect so we add the
1325 * "curve" param to allow updates in the LPE results at this stage
1326 * for groups dont need to send "curve" parameter because is applied
1327 * when the LPE process finish, we can update LPE results without "curve" parameter
1328 * @param lpeitem the element that has this LPE
1329 * @param curve the curve to pass when in mode path or shape
1330 */
doAfterEffect(SPLPEItem const *,SPCurve * curve)1331 void Effect::doAfterEffect (SPLPEItem const* /*lpeitem*/, SPCurve *curve)
1332 {
1333 //Do nothing for simple effects
1334 }
1335
doOnException(SPLPEItem const *)1336 void Effect::doOnException(SPLPEItem const * /*lpeitem*/)
1337 {
1338 has_exception = true;
1339 pathvector_after_effect = pathvector_before_effect;
1340 }
1341
1342
doOnRemove(SPLPEItem const *)1343 void Effect::doOnRemove (SPLPEItem const* /*lpeitem*/)
1344 {
1345 }
doOnVisibilityToggled(SPLPEItem const *)1346 void Effect::doOnVisibilityToggled(SPLPEItem const* /*lpeitem*/)
1347 {
1348 }
1349 //secret impl methods (shhhh!)
doAfterEffect_impl(SPLPEItem const * lpeitem,SPCurve * curve)1350 void Effect::doAfterEffect_impl(SPLPEItem const *lpeitem, SPCurve *curve)
1351 {
1352 doAfterEffect(lpeitem, curve);
1353 is_load = false;
1354 is_applied = false;
1355 }
doOnApply_impl(SPLPEItem const * lpeitem)1356 void Effect::doOnApply_impl(SPLPEItem const* lpeitem)
1357 {
1358 sp_lpe_item = const_cast<SPLPEItem *>(lpeitem);
1359 is_applied = true;
1360 // we can override "lpeversion" value in each LPE using doOnApply
1361 // this allow to handle legacy LPE and some times update to newest definitions
1362 // In BBB Martin, Mc and Jabiertxof make decission
1363 // of only update this value per each LPE when changes.
1364 // and use the Inkscape release version that has this new LPE change
1365 // LPE without lpeversion are created in a inkscape lower than 1.0
1366 lpeversion.param_setValue("1", true);
1367 doOnApply(lpeitem);
1368 setReady();
1369 has_exception = false;
1370 }
1371
doBeforeEffect_impl(SPLPEItem const * lpeitem)1372 void Effect::doBeforeEffect_impl(SPLPEItem const* lpeitem)
1373 {
1374 sp_lpe_item = const_cast<SPLPEItem *>(lpeitem);
1375 doBeforeEffect(lpeitem);
1376 update_helperpath();
1377 }
1378
1379 void
writeParamsToSVG()1380 Effect::writeParamsToSVG() {
1381 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
1382 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
1383 (*p)->write_to_SVG();
1384 }
1385 }
1386
1387 /**
1388 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
1389 * applied, this is the method that processes the resulting path. Override it to customize it for
1390 * your LPE. But don't forget to call the parent method so that is_ready is set to true!
1391 */
1392 void
acceptParamPath(SPPath const *)1393 Effect::acceptParamPath (SPPath const*/*param_path*/) {
1394 setReady();
1395 }
1396
1397 /*
1398 * Here be the doEffect function chain:
1399 */
1400 void
doEffect(SPCurve * curve)1401 Effect::doEffect (SPCurve * curve)
1402 {
1403 Geom::PathVector orig_pathv = curve->get_pathvector();
1404
1405 Geom::PathVector result_pathv = doEffect_path(orig_pathv);
1406
1407 curve->set_pathvector(result_pathv);
1408 }
1409
1410 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)1411 Effect::doEffect_path (Geom::PathVector const & path_in)
1412 {
1413 Geom::PathVector path_out;
1414
1415 if ( !concatenate_before_pwd2 ) {
1416 // default behavior
1417 for (const auto & i : path_in) {
1418 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = i.toPwSb();
1419 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
1420 Geom::PathVector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
1421 // add the output path vector to the already accumulated vector:
1422 for (const auto & j : path) {
1423 path_out.push_back(j);
1424 }
1425 }
1426 } else {
1427 // concatenate the path into possibly discontinuous pwd2
1428 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
1429 for (const auto & i : path_in) {
1430 pwd2_in.concat( i.toPwSb() );
1431 }
1432 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
1433 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
1434 }
1435
1436 return path_out;
1437 }
1438
1439 Geom::Piecewise<Geom::D2<Geom::SBasis> >
doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis>> const & pwd2_in)1440 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
1441 {
1442 g_warning("Effect has no doEffect implementation");
1443 return pwd2_in;
1444 }
1445
1446 void
readallParameters(Inkscape::XML::Node const * repr)1447 Effect::readallParameters(Inkscape::XML::Node const* repr)
1448 {
1449 std::vector<Parameter *>::iterator it = param_vector.begin();
1450 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1451 while (it != param_vector.end()) {
1452 Parameter * param = *it;
1453 const gchar * key = param->param_key.c_str();
1454 const gchar * value = repr->attribute(key);
1455 if (value) {
1456 bool accepted = param->param_readSVGValue(value);
1457 if (!accepted) {
1458 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
1459 }
1460 } else {
1461 Glib::ustring pref_path = (Glib::ustring)"/live_effects/" +
1462 (Glib::ustring)LPETypeConverter.get_key(effectType()).c_str() +
1463 (Glib::ustring)"/" +
1464 (Glib::ustring)key;
1465 bool valid = prefs->getEntry(pref_path).isValid();
1466 if(valid){
1467 param->param_update_default(prefs->getString(pref_path).c_str());
1468 } else {
1469 param->param_set_default();
1470 }
1471 }
1472 ++it;
1473 }
1474 }
1475
1476 /* This function does not and SHOULD NOT write to XML */
1477 void
setParameter(const gchar * key,const gchar * new_value)1478 Effect::setParameter(const gchar * key, const gchar * new_value)
1479 {
1480 Parameter * param = getParameter(key);
1481 if (param) {
1482 if (new_value) {
1483 bool accepted = param->param_readSVGValue(new_value);
1484 if (!accepted) {
1485 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
1486 }
1487 } else {
1488 // set default value
1489 param->param_set_default();
1490 }
1491 }
1492 }
1493
1494 void
registerParameter(Parameter * param)1495 Effect::registerParameter(Parameter * param)
1496 {
1497 param_vector.push_back(param);
1498 }
1499
1500
1501 /**
1502 * Add all registered LPE knotholder handles to the knotholder
1503 */
1504 void
addHandles(KnotHolder * knotholder,SPItem * item)1505 Effect::addHandles(KnotHolder *knotholder, SPItem *item) {
1506 using namespace Inkscape::LivePathEffect;
1507
1508 // add handles provided by the effect itself
1509 addKnotHolderEntities(knotholder, item);
1510
1511 // add handles provided by the effect's parameters (if any)
1512 for (auto & p : param_vector) {
1513 p->addKnotHolderEntities(knotholder, item);
1514 }
1515 if (is_load) {
1516 SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
1517 if (lpeitem) {
1518 sp_lpe_item_update_patheffect(lpeitem, false, false);
1519 }
1520 }
1521 }
1522
1523 /**
1524 * Return a vector of PathVectors which contain all canvas indicators for this effect.
1525 * This is the function called by external code to get all canvas indicators (effect and its parameters)
1526 * lpeitem = the item onto which this effect is applied
1527 * @todo change return type to one pathvector, add all paths to one pathvector instead of maintaining a vector of pathvectors
1528 */
1529 std::vector<Geom::PathVector>
getCanvasIndicators(SPLPEItem const * lpeitem)1530 Effect::getCanvasIndicators(SPLPEItem const* lpeitem)
1531 {
1532 std::vector<Geom::PathVector> hp_vec;
1533
1534 // add indicators provided by the effect itself
1535 addCanvasIndicators(lpeitem, hp_vec);
1536
1537 // add indicators provided by the effect's parameters
1538 for (auto & p : param_vector) {
1539 p->addCanvasIndicators(lpeitem, hp_vec);
1540 }
1541 Geom::Affine scale = lpeitem->i2doc_affine();
1542 for (auto &path : hp_vec) {
1543 path *= scale;
1544 }
1545 return hp_vec;
1546 }
1547
1548 /**
1549 * Add possible canvas indicators (i.e., helperpaths other than the original path) to \a hp_vec
1550 * This function should be overwritten by derived effects if they want to provide their own helperpaths.
1551 */
1552 void
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> &)1553 Effect::addCanvasIndicators(SPLPEItem const*/*lpeitem*/, std::vector<Geom::PathVector> &/*hp_vec*/)
1554 {
1555 }
1556
1557 /**
1558 * Call to a method on nodetool to update the helper path from the effect
1559 */
1560 void
update_helperpath()1561 Effect::update_helperpath() {
1562 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1563 if (desktop) {
1564 Inkscape::UI::Tools::NodeTool *nt = dynamic_cast<Inkscape::UI::Tools::NodeTool*>(desktop->event_context);
1565 if (nt) {
1566 Inkscape::UI::Tools::sp_update_helperpath(desktop);
1567 }
1568 }
1569 }
1570
1571 /**
1572 * This *creates* a new widget, management of deletion should be done by the caller
1573 */
1574 Gtk::Widget *
newWidget()1575 Effect::newWidget()
1576 {
1577 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
1578 Gtk::Box * vbox = Gtk::manage( new Gtk::Box(Gtk::ORIENTATION_VERTICAL) );
1579
1580 vbox->set_border_width(5);
1581
1582 std::vector<Parameter *>::iterator it = param_vector.begin();
1583 while (it != param_vector.end()) {
1584 if ((*it)->widget_is_visible) {
1585 Parameter * param = *it;
1586 Gtk::Widget * widg = param->param_newWidget();
1587 Glib::ustring * tip = param->param_getTooltip();
1588 if (widg) {
1589 if (param->widget_is_enabled) {
1590 widg->set_sensitive(true);
1591 } else {
1592 widg->set_sensitive(false);
1593 }
1594 vbox->pack_start(*widg, true, true, 2);
1595 if (tip) {
1596 widg->set_tooltip_text(*tip);
1597 } else {
1598 widg->set_tooltip_text("");
1599 widg->set_has_tooltip(false);
1600 }
1601 }
1602 }
1603
1604 ++it;
1605 }
1606 if(Gtk::Widget* widg = defaultParamSet()) {
1607 vbox->pack_start(*widg, true, true, 2);
1608 }
1609 return dynamic_cast<Gtk::Widget *>(vbox);
1610 }
1611
sp_enter_tooltip(GdkEventCrossing * evt,Gtk::Widget * widg)1612 bool sp_enter_tooltip(GdkEventCrossing *evt, Gtk::Widget *widg)
1613 {
1614 widg->trigger_tooltip_query();
1615 return true;
1616 }
1617
1618 /**
1619 * This *creates* a new widget, with default values setter
1620 */
1621 Gtk::Widget *
defaultParamSet()1622 Effect::defaultParamSet()
1623 {
1624 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
1625 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1626 Gtk::Box * vbox_expander = Gtk::manage( new Gtk::Box(Gtk::ORIENTATION_VERTICAL) );
1627 Glib::ustring effectname = (Glib::ustring)Inkscape::LivePathEffect::LPETypeConverter.get_label(effectType());
1628 Glib::ustring effectkey = (Glib::ustring)Inkscape::LivePathEffect::LPETypeConverter.get_key(effectType());
1629 std::vector<Parameter *>::iterator it = param_vector.begin();
1630 bool has_params = false;
1631 while (it != param_vector.end()) {
1632 if ((*it)->widget_is_visible) {
1633 has_params = true;
1634 Parameter * param = *it;
1635 const gchar * key = param->param_key.c_str();
1636 if (g_strcmp0(key, "lpeversion") == 0) {
1637 ++it;
1638 continue;
1639 }
1640 const gchar * label = param->param_label.c_str();
1641 Glib::ustring value = param->param_getSVGValue();
1642 Glib::ustring defvalue = param->param_getDefaultSVGValue();
1643 Glib::ustring pref_path = "/live_effects/";
1644 pref_path += effectkey;
1645 pref_path +="/";
1646 pref_path += key;
1647 bool valid = prefs->getEntry(pref_path).isValid();
1648 const gchar * set_or_upd;
1649 Glib::ustring def = Glib::ustring(_("<b>Default value:</b> ")) + defvalue;
1650 Glib::ustring ove = Glib::ustring(_("<b>Default value overridden:</b> "));
1651 if (valid) {
1652 set_or_upd = _("Update");
1653 def = "";
1654 } else {
1655 set_or_upd = _("Set");
1656 ove = "";
1657 }
1658 Gtk::Box * vbox_param = Gtk::manage( new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) );
1659 Gtk::Box *namedicon = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
1660 Gtk::Label *parameter_label = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_START));
1661 parameter_label->set_use_markup(true);
1662 parameter_label->set_use_underline(true);
1663 parameter_label->set_ellipsize(Pango::ELLIPSIZE_END);
1664 Glib::ustring tooltip = Glib::ustring("<b>") + parameter_label->get_text() + Glib::ustring("</b>\n") +
1665 param->param_tooltip + Glib::ustring("\n");
1666 Gtk::Image *info = sp_get_icon_image("info", 20);
1667 Gtk::EventBox *infoeventbox = Gtk::manage(new Gtk::EventBox());
1668 infoeventbox->add(*info);
1669 infoeventbox->set_tooltip_markup((tooltip + def + ove).c_str());
1670 namedicon->pack_start(*infoeventbox, false, false, 2);
1671 namedicon->pack_start(*parameter_label, true, true, 2);
1672 namedicon->set_homogeneous(false);
1673 vbox_param->pack_start(*namedicon, true, true, 2);
1674 Gtk::Button *set = Gtk::manage(new Gtk::Button((Glib::ustring)set_or_upd));
1675 Gtk::Button *unset = Gtk::manage(new Gtk::Button(Glib::ustring(_("Unset"))));
1676 unset->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Effect::unsetDefaultParam), pref_path,
1677 tooltip, param, info, set, unset));
1678 set->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Effect::setDefaultParam), pref_path, tooltip,
1679 param, info, set, unset));
1680 if (!valid) {
1681 unset->set_sensitive(false);
1682 }
1683 unset->set_size_request (90, -1);
1684 set->set_size_request (90, -1);
1685 vbox_param->pack_end(*unset, false, true, 2);
1686 vbox_param->pack_end(*set, false, true, 2);
1687
1688 vbox_expander->pack_start(*vbox_param, true, true, 2);
1689 }
1690 ++it;
1691 }
1692 Glib::ustring tip = "<b>" + effectname + (Glib::ustring)_("</b>: Set default parameters");
1693 Gtk::Expander * expander = Gtk::manage(new Gtk::Expander(tip));
1694 expander->set_use_markup(true);
1695 expander->add(*vbox_expander);
1696 expander->set_expanded(defaultsopen);
1697 expander->property_expanded().signal_changed().connect(sigc::bind<0>(sigc::mem_fun(*this, &Effect::onDefaultsExpanderChanged), expander ));
1698 if (has_params) {
1699 Gtk::Widget *vboxwidg = dynamic_cast<Gtk::Widget *>(expander);
1700 vboxwidg->set_margin_bottom(5);
1701 vboxwidg->set_margin_top(5);
1702 return vboxwidg;
1703 } else {
1704 return nullptr;
1705 }
1706 }
1707
1708 void
onDefaultsExpanderChanged(Gtk::Expander * expander)1709 Effect::onDefaultsExpanderChanged(Gtk::Expander * expander)
1710 {
1711 defaultsopen = expander->get_expanded();
1712 }
1713
setDefaultParam(Glib::ustring pref_path,Glib::ustring tooltip,Parameter * param,Gtk::Image * info,Gtk::Button * set,Gtk::Button * unset)1714 void Effect::setDefaultParam(Glib::ustring pref_path, Glib::ustring tooltip, Parameter *param, Gtk::Image *info,
1715 Gtk::Button *set, Gtk::Button *unset)
1716 {
1717 Glib::ustring value = param->param_getSVGValue();
1718 Glib::ustring defvalue = param->param_getDefaultSVGValue();
1719 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1720 prefs->setString(pref_path, value);
1721 gchar * label = _("Update");
1722 set->set_label((Glib::ustring)label);
1723 unset->set_sensitive(true);
1724 Glib::ustring ove = Glib::ustring(_("<b>Default value overridden:</b> ")) + value;
1725 info->set_tooltip_markup((tooltip + ove).c_str());
1726 }
1727
unsetDefaultParam(Glib::ustring pref_path,Glib::ustring tooltip,Parameter * param,Gtk::Image * info,Gtk::Button * set,Gtk::Button * unset)1728 void Effect::unsetDefaultParam(Glib::ustring pref_path, Glib::ustring tooltip, Parameter *param, Gtk::Image *info,
1729 Gtk::Button *set, Gtk::Button *unset)
1730 {
1731 Glib::ustring value = param->param_getSVGValue();
1732 Glib::ustring defvalue = param->param_getDefaultSVGValue();
1733 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1734 prefs->remove(pref_path);
1735 gchar * label = _("Set");
1736 set->set_label((Glib::ustring)label);
1737 unset->set_sensitive(false);
1738 Glib::ustring def = Glib::ustring(_("<b>Default value:</b> Default"));
1739 info->set_tooltip_markup((tooltip + def).c_str());
1740 }
1741
getRepr()1742 Inkscape::XML::Node *Effect::getRepr()
1743 {
1744 return lpeobj->getRepr();
1745 }
1746
getSPDoc()1747 SPDocument *Effect::getSPDoc()
1748 {
1749 if (lpeobj->document == nullptr) {
1750 g_message("Effect::getSPDoc() returns NULL");
1751 }
1752 return lpeobj->document;
1753 }
1754
1755 Parameter *
getParameter(const char * key)1756 Effect::getParameter(const char * key)
1757 {
1758 Glib::ustring stringkey(key);
1759
1760 if (param_vector.empty()) return nullptr;
1761 std::vector<Parameter *>::iterator it = param_vector.begin();
1762 while (it != param_vector.end()) {
1763 Parameter * param = *it;
1764 if ( param->param_key == key) {
1765 return param;
1766 }
1767
1768 ++it;
1769 }
1770
1771 return nullptr;
1772 }
1773
1774 Parameter *
getNextOncanvasEditableParam()1775 Effect::getNextOncanvasEditableParam()
1776 {
1777 if (param_vector.size() == 0) // no parameters
1778 return nullptr;
1779
1780 oncanvasedit_it++;
1781 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
1782 oncanvasedit_it = 0;
1783 }
1784 int old_it = oncanvasedit_it;
1785
1786 do {
1787 Parameter * param = param_vector[oncanvasedit_it];
1788 if(param && param->oncanvas_editable) {
1789 return param;
1790 } else {
1791 oncanvasedit_it++;
1792 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
1793 oncanvasedit_it = 0;
1794 }
1795 }
1796 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
1797
1798 return nullptr;
1799 }
1800
1801 void
editNextParamOncanvas(SPItem * item,SPDesktop * desktop)1802 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
1803 {
1804 if (!desktop) return;
1805
1806 Parameter * param = getNextOncanvasEditableParam();
1807 if (param) {
1808 param->param_editOncanvas(item, desktop);
1809 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
1810 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
1811 g_free(message);
1812 } else {
1813 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
1814 _("None of the applied path effect's parameters can be edited on-canvas.") );
1815 }
1816 }
1817
1818 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
1819 * The nice thing about this is that this function can use knowledge of the original path and set things accordingly for example to the size or origin of the original path!
1820 */
1821 void
resetDefaults(SPItem const *)1822 Effect::resetDefaults(SPItem const* /*item*/)
1823 {
1824 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
1825 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
1826 (*p)->param_set_default();
1827 (*p)->write_to_SVG();
1828 }
1829 }
1830
1831 bool
providesKnotholder() const1832 Effect::providesKnotholder() const
1833 {
1834 // does the effect actively provide any knotholder entities of its own?
1835 if (_provides_knotholder_entities) {
1836 return true;
1837 }
1838
1839 // otherwise: are there any parameters that have knotholderentities?
1840 for (auto p : param_vector) {
1841 if (p->providesKnotHolderEntities()) {
1842 return true;
1843 }
1844 }
1845
1846 return false;
1847 }
1848
1849 } /* namespace LivePathEffect */
1850
1851 } /* namespace Inkscape */
1852
1853 /*
1854 Local Variables:
1855 mode:c++
1856 c-file-style:"stroustrup"
1857 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1858 indent-tabs-mode:nil
1859 fill-column:99
1860 End:
1861 */
1862 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
1863