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