1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Generic drawing context
4  *
5  * Author:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   Abhishek Sharma
8  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2000 Lauris Kaplinski
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  * Copyright (C) 2002 Lauris Kaplinski
13  * Copyright (C) 2012 Johan Engelen
14  *
15  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16  */
17 
18 #define DRAW_VERBOSE
19 
20 #include "desktop-style.h"
21 #include "message-stack.h"
22 #include "selection-chemistry.h"
23 
24 #include "display/curve.h"
25 #include "display/control/canvas-item-bpath.h"
26 
27 #include "include/macros.h"
28 
29 #include "live_effects/lpe-bendpath.h"
30 #include "live_effects/lpe-patternalongpath.h"
31 #include "live_effects/lpe-simplify.h"
32 #include "live_effects/lpe-powerstroke.h"
33 
34 #include "svg/svg-color.h"
35 #include "svg/svg.h"
36 
37 #include "id-clash.h"
38 #include "object/sp-item-group.h"
39 #include "object/sp-path.h"
40 #include "object/sp-rect.h"
41 #include "object/sp-use.h"
42 #include "style.h"
43 
44 #include "ui/clipboard.h"
45 #include "ui/draw-anchor.h"
46 #include "ui/tools/lpe-tool.h"
47 #include "ui/tools/pen-tool.h"
48 #include "ui/tools/pencil-tool.h"
49 
50 #define MIN_PRESSURE      0.0
51 #define MAX_PRESSURE      1.0
52 #define DEFAULT_PRESSURE  1.0
53 
54 using Inkscape::DocumentUndo;
55 
56 namespace Inkscape {
57 namespace UI {
58 namespace Tools {
59 
60 static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc);
61 static void spdc_selection_modified(Inkscape::Selection *sel, guint flags, FreehandBase *dc);
62 
63 static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection *sel);
64 
65 /**
66  * Flushes white curve(s) and additional curve into object.
67  *
68  * No cleaning of colored curves - this has to be done by caller
69  * No rereading of white data, so if you cannot rely on ::modified, do it in caller
70  */
71 static void spdc_flush_white(FreehandBase *dc, SPCurve *gc);
72 
73 static void spdc_reset_white(FreehandBase *dc);
74 static void spdc_free_colors(FreehandBase *dc);
75 
FreehandBase(const std::string & cursor_filename)76 FreehandBase::FreehandBase(const std::string& cursor_filename)
77     : ToolBase(cursor_filename)
78     , selection(nullptr)
79     , attach(false)
80     , red_color(0xff00007f)
81     , blue_color(0x0000ff7f)
82     , green_color(0x00ff007f)
83     , highlight_color(0x0000007f)
84     , red_bpath(nullptr)
85     , red_curve(nullptr)
86     , blue_bpath(nullptr)
87     , blue_curve(nullptr)
88     , green_curve(nullptr)
89     , green_anchor(nullptr)
90     , green_closed(false)
91     , white_item(nullptr)
92     , sa_overwrited(nullptr)
93     , sa(nullptr)
94     , ea(nullptr)
95     , waiting_LPE_type(Inkscape::LivePathEffect::INVALID_LPE)
96     , red_curve_is_valid(false)
97     , anchor_statusbar(false)
98     , tablet_enabled(false)
99     , is_tablet(false)
100     , pressure(DEFAULT_PRESSURE)
101 {
102 }
103 
~FreehandBase()104 FreehandBase::~FreehandBase() {
105     ungrabCanvasEvents();
106 
107     if (this->selection) {
108         this->selection = nullptr;
109     }
110 
111     spdc_free_colors(this);
112 }
113 
setup()114 void FreehandBase::setup() {
115     ToolBase::setup();
116 
117     this->selection = desktop->getSelection();
118 
119     // Connect signals to track selection changes
120     this->sel_changed_connection = this->selection->connectChanged(
121         sigc::bind(sigc::ptr_fun(&spdc_selection_changed), this)
122     );
123     this->sel_modified_connection = this->selection->connectModified(
124         sigc::bind(sigc::ptr_fun(&spdc_selection_modified), this)
125     );
126 
127     // Create red bpath
128     this->red_bpath = new Inkscape::CanvasItemBpath(desktop->getCanvasSketch());
129     this->red_bpath->set_stroke(this->red_color);
130     this->red_bpath->set_fill(0x0, SP_WIND_RULE_NONZERO);
131 
132     // Create red curve
133     this->red_curve.reset(new SPCurve());
134 
135     // Create blue bpath
136     this->blue_bpath = new Inkscape::CanvasItemBpath(desktop->getCanvasSketch());
137     this->blue_bpath->set_stroke(this->blue_color);
138     this->blue_bpath->set_fill(0x0, SP_WIND_RULE_NONZERO);
139 
140     // Create blue curve
141     this->blue_curve.reset(new SPCurve());
142 
143     // Create green curve
144     this->green_curve.reset(new SPCurve());
145 
146     // No green anchor by default
147     this->green_anchor = nullptr;
148     this->green_closed = FALSE;
149 
150     // Create start anchor alternative curve
151     this->sa_overwrited.reset(new SPCurve());
152 
153     this->attach = TRUE;
154     spdc_attach_selection(this, this->selection);
155 }
156 
finish()157 void FreehandBase::finish() {
158     this->sel_changed_connection.disconnect();
159     this->sel_modified_connection.disconnect();
160 
161     ungrabCanvasEvents();
162 
163     if (this->selection) {
164         this->selection = nullptr;
165     }
166 
167     spdc_free_colors(this);
168 
169     ToolBase::finish();
170 }
171 
set(const Inkscape::Preferences::Entry &)172 void FreehandBase::set(const Inkscape::Preferences::Entry& /*value*/) {
173 }
174 
root_handler(GdkEvent * event)175 bool FreehandBase::root_handler(GdkEvent* event) {
176     gint ret = FALSE;
177 
178     switch (event->type) {
179         case GDK_KEY_PRESS:
180             switch (get_latin_keyval (&event->key)) {
181                 case GDK_KEY_Up:
182                 case GDK_KEY_Down:
183                 case GDK_KEY_KP_Up:
184                 case GDK_KEY_KP_Down:
185                     // prevent the zoom field from activation
186                     if (!MOD__CTRL_ONLY(event)) {
187                         ret = TRUE;
188                     }
189                     break;
190                 default:
191             break;
192         }
193         break;
194     default:
195         break;
196     }
197 
198     if (!ret) {
199     	ret = ToolBase::root_handler(event);
200     }
201 
202     return ret;
203 }
204 
red_curve_get_last_point()205 std::optional<Geom::Point> FreehandBase::red_curve_get_last_point()
206 {
207     std::optional<Geom::Point> p;
208     if (!red_curve->is_empty()) {
209         p = red_curve->last_point();
210     }
211     return p;
212 }
213 
tool_name(FreehandBase * dc)214 static Glib::ustring const tool_name(FreehandBase *dc)
215 {
216     return ( SP_IS_PEN_CONTEXT(dc)
217              ? "/tools/freehand/pen"
218              : "/tools/freehand/pencil" );
219 }
220 
spdc_paste_curve_as_freehand_shape(Geom::PathVector const & newpath,FreehandBase * dc,SPItem * item)221 static void spdc_paste_curve_as_freehand_shape(Geom::PathVector const &newpath, FreehandBase *dc, SPItem *item)
222 {
223     using namespace Inkscape::LivePathEffect;
224 
225     // TODO: Don't paste path if nothing is on the clipboard
226     SPDocument *document = dc->getDesktop()->doc();
227     bool saved = DocumentUndo::getUndoSensitive(document);
228     DocumentUndo::setUndoSensitive(document, false);
229     Effect::createAndApply(PATTERN_ALONG_PATH, document, item);
230     Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
231     static_cast<LPEPatternAlongPath*>(lpe)->pattern.set_new_value(newpath,true);
232     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
233     double scale = prefs->getDouble("/live_effects/skeletal/width", 1);
234     if (!scale) {
235         scale = 1;
236     }
237     Inkscape::SVGOStringStream os;
238     os << scale;
239     lpe->getRepr()->setAttribute("prop_scale", os.str());
240     DocumentUndo::setUndoSensitive(document, saved);
241 }
242 
spdc_apply_style(SPObject * obj)243 void spdc_apply_style(SPObject *obj)
244 {
245     SPCSSAttr *css = sp_repr_css_attr_new();
246     if (obj->style) {
247         if (obj->style->stroke.isPaintserver()) {
248             SPPaintServer *server = obj->style->getStrokePaintServer();
249             if (server) {
250                 Glib::ustring str;
251                 str += "url(#";
252                 str += server->getId();
253                 str += ")";
254                 sp_repr_css_set_property(css, "fill", str.c_str());
255             }
256         } else if (obj->style->stroke.isColor()) {
257             gchar c[64];
258             sp_svg_write_color(
259                 c, sizeof(c),
260                 obj->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(obj->style->stroke_opacity.value)));
261             sp_repr_css_set_property(css, "fill", c);
262         } else {
263             sp_repr_css_set_property(css, "fill", "none");
264         }
265     } else {
266         sp_repr_css_unset_property(css, "fill");
267     }
268 
269     sp_repr_css_set_property(css, "fill-rule", "nonzero");
270     sp_repr_css_set_property(css, "stroke", "none");
271 
272     sp_desktop_apply_css_recursive(obj, css, true);
273     sp_repr_css_attr_unref(css);
274 }
spdc_apply_powerstroke_shape(std::vector<Geom::Point> points,FreehandBase * dc,SPItem * item,gint maxrecursion=0)275 static void spdc_apply_powerstroke_shape(std::vector<Geom::Point> points, FreehandBase *dc, SPItem *item,
276                                          gint maxrecursion = 0)
277 {
278     using namespace Inkscape::LivePathEffect;
279     SPDesktop *desktop = dc->getDesktop();
280     SPDocument *document = desktop->getDocument();
281     if (!document || !desktop) {
282         return;
283     }
284     if (SP_IS_PENCIL_CONTEXT(dc)) {
285         if (dc->tablet_enabled) {
286             SPObject *elemref = nullptr;
287             if ((elemref = document->getObjectById("power_stroke_preview"))) {
288                 elemref->getRepr()->removeAttribute("style");
289                 SPItem *successor = dynamic_cast<SPItem *>(elemref);
290                 sp_desktop_apply_style_tool(desktop, successor->getRepr(),
291                                             Glib::ustring("/tools/freehand/pencil").data(), false);
292                 spdc_apply_style(successor);
293                 sp_object_ref(item);
294                 item->deleteObject(false);
295                 item->setSuccessor(successor);
296                 sp_object_unref(item);
297                 item = dynamic_cast<SPItem *>(successor);
298                 dc->selection->set(item);
299                 item->setLocked(false);
300                 dc->white_item = item;
301                 rename_id(SP_OBJECT(item), "path-1");
302             }
303             return;
304         }
305     }
306     bool saved = DocumentUndo::getUndoSensitive(document);
307     DocumentUndo::setUndoSensitive(document, false);
308     Effect::createAndApply(POWERSTROKE, document, item);
309     Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
310 
311     static_cast<LPEPowerStroke*>(lpe)->offset_points.param_set_and_write_new_value(points);
312 
313     // write powerstroke parameters:
314     lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth");
315     lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth");
316     lpe->getRepr()->setAttribute("sort_points", "true");
317     lpe->getRepr()->setAttribute("not_jump", "false");
318     lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan");
319     lpe->getRepr()->setAttribute("interpolator_beta", "0.2");
320     lpe->getRepr()->setAttribute("miter_limit", "4");
321     lpe->getRepr()->setAttribute("scale_width", "1");
322     lpe->getRepr()->setAttribute("linejoin_type", "extrp_arc");
323     DocumentUndo::setUndoSensitive(document, saved);
324 }
325 
spdc_apply_bend_shape(gchar const * svgd,FreehandBase * dc,SPItem * item)326 static void spdc_apply_bend_shape(gchar const *svgd, FreehandBase *dc, SPItem *item)
327 {
328     using namespace Inkscape::LivePathEffect;
329     SPUse *use = dynamic_cast<SPUse *>(item);
330     if ( use ) {
331         return;
332     }
333     SPDesktop *desktop = dc->getDesktop();
334     SPDocument *document = desktop->getDocument();
335     if (!document || !desktop) {
336         return;
337     }
338     bool saved = DocumentUndo::getUndoSensitive(document);
339     DocumentUndo::setUndoSensitive(document, false);
340     if(!SP_IS_LPE_ITEM(item) || !SP_LPE_ITEM(item)->hasPathEffectOfType(BEND_PATH)){
341         Effect::createAndApply(BEND_PATH, document, item);
342     }
343     Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
344 
345     // write bend parameters:
346     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
347     double scale = prefs->getDouble("/live_effects/bend_path/width", 1);
348     if (!scale) {
349         scale = 1;
350     }
351     Inkscape::SVGOStringStream os;
352     os << scale;
353     lpe->getRepr()->setAttribute("prop_scale", os.str());
354     lpe->getRepr()->setAttribute("scale_y_rel", "false");
355     lpe->getRepr()->setAttribute("vertical", "false");
356     static_cast<LPEBendPath*>(lpe)->bend_path.paste_param_path(svgd);
357     DocumentUndo::setUndoSensitive(document, saved);
358 }
359 
spdc_apply_simplify(std::string threshold,FreehandBase * dc,SPItem * item)360 static void spdc_apply_simplify(std::string threshold, FreehandBase *dc, SPItem *item)
361 {
362     const SPDesktop *desktop = dc->getDesktop();
363     SPDocument *document = desktop->getDocument();
364     if (!document || !desktop) {
365         return;
366     }
367     bool saved = DocumentUndo::getUndoSensitive(document);
368     DocumentUndo::setUndoSensitive(document, false);
369     using namespace Inkscape::LivePathEffect;
370 
371     Effect::createAndApply(SIMPLIFY, document, item);
372     Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
373     // write simplify parameters:
374     lpe->getRepr()->setAttribute("steps", "1");
375     lpe->getRepr()->setAttributeOrRemoveIfEmpty("threshold", threshold);
376     lpe->getRepr()->setAttribute("smooth_angles", "360");
377     lpe->getRepr()->setAttribute("helper_size", "0");
378     lpe->getRepr()->setAttribute("simplify_individual_paths", "false");
379     lpe->getRepr()->setAttribute("simplify_just_coalesce", "false");
380     DocumentUndo::setUndoSensitive(document, saved);
381 }
382 
383 static shapeType previous_shape_type = NONE;
384 
spdc_check_for_and_apply_waiting_LPE(FreehandBase * dc,SPItem * item,SPCurve * curve,bool is_bend)385 static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve *curve, bool is_bend)
386 {
387     using namespace Inkscape::LivePathEffect;
388     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
389 
390     auto *desktop = dc->getDesktop();
391 
392     if (item && SP_IS_LPE_ITEM(item)) {
393         double defsize = 10 / (0.265 * dc->getDesktop()->getDocument()->getDocumentScale()[0]);
394 #define SHAPE_LENGTH defsize
395 #define SHAPE_HEIGHT defsize
396         //Store the clipboard path to apply in the future without the use of clipboard
397         static Geom::PathVector previous_shape_pathv;
398         static SPItem *bend_item;
399         shapeType shape = (shapeType)prefs->getInt(tool_name(dc) + "/shape", 0);
400         if (previous_shape_type == NONE) {
401             previous_shape_type = shape;
402         }
403         if(shape == LAST_APPLIED){
404             shape = previous_shape_type;
405             if(shape == CLIPBOARD || shape == BEND_CLIPBOARD){
406                 shape = LAST_APPLIED;
407             }
408         }
409         Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
410         if (is_bend &&
411            (shape == BEND_CLIPBOARD || (shape == LAST_APPLIED && previous_shape_type != CLIPBOARD)) &&
412             cm->paste(desktop, true))
413         {
414             bend_item = dc->selection->singleItem();
415             if(!bend_item || (!SP_IS_SHAPE(bend_item) && !SP_IS_GROUP(bend_item))){
416                 previous_shape_type = NONE;
417                 return;
418             }
419         } else if(is_bend) {
420             return;
421         }
422         if (!is_bend && previous_shape_type == BEND_CLIPBOARD && shape == BEND_CLIPBOARD) {
423             return;
424         }
425         bool shape_applied = false;
426         bool simplify = prefs->getInt(tool_name(dc) + "/simplify", 0);
427         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
428         guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
429         if(simplify && mode != 2){
430             double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0);
431             tol = tol/(100.0*(102.0-tol));
432             std::ostringstream ss;
433             ss << tol;
434             spdc_apply_simplify(ss.str(), dc, item);
435             sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false);
436         }
437         if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) {
438             Effect::createAndApply(SPIRO, dc->getDesktop()->getDocument(), item);
439         }
440 
441         if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) {
442             Effect::createAndApply(BSPLINE, dc->getDesktop()->getDocument(), item);
443         }
444         SPShape *sp_shape = dynamic_cast<SPShape *>(item);
445         if (sp_shape) {
446             curve = sp_shape->curve();
447         }
448         auto curveref = curve->ref();
449         SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS);
450         const char *cstroke = sp_repr_css_property(css_item, "stroke", "none");
451         const char *cfill = sp_repr_css_property(css_item, "fill", "none");
452         const char *stroke_width = sp_repr_css_property(css_item, "stroke-width", "0");
453         double swidth;
454         sp_svg_number_read_d(stroke_width, &swidth);
455         swidth = prefs->getDouble("/live_effects/powerstroke/width", SHAPE_HEIGHT / 2);
456         if (!swidth) {
457             swidth = swidth/2;
458         }
459         swidth = std::abs(swidth);
460         if (SP_IS_PENCIL_CONTEXT(dc)) {
461             if (dc->tablet_enabled) {
462                 std::vector<Geom::Point> points;
463                 spdc_apply_powerstroke_shape(points, dc, item);
464                 shape_applied = true;
465                 shape = NONE;
466                 previous_shape_type = NONE;
467             }
468         }
469 
470         switch (shape) {
471             case NONE:
472                 // don't apply any shape
473                 break;
474             case TRIANGLE_IN:
475             {
476                 // "triangle in"
477                 std::vector<Geom::Point> points(1);
478 
479                 points[0] = Geom::Point(0., swidth);
480                 //points[0] *= i2anc_affine(static_cast<SPItem *>(item->parent), NULL).inverse();
481                 spdc_apply_powerstroke_shape(points, dc, item);
482 
483                 shape_applied = false;
484                 break;
485             }
486             case TRIANGLE_OUT:
487             {
488                 // "triangle out"
489                 guint curve_length = curveref->get_segment_count();
490                 std::vector<Geom::Point> points(1);
491                 points[0] = Geom::Point(0, swidth);
492                 //points[0] *= i2anc_affine(static_cast<SPItem *>(item->parent), NULL).inverse();
493                 points[0][Geom::X] = (double)curve_length;
494                 spdc_apply_powerstroke_shape(points, dc, item);
495 
496                 shape_applied = false;
497                 break;
498             }
499             case ELLIPSE:
500             {
501                 // "ellipse"
502                 auto c = std::make_unique<SPCurve>();
503                 const double C1 = 0.552;
504                 c->moveto(0, SHAPE_HEIGHT/2);
505                 c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0);
506                 c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2);
507                 c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT);
508                 c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2);
509                 c->closepath();
510                 spdc_paste_curve_as_freehand_shape(c->get_pathvector(), dc, item);
511 
512                 shape_applied = true;
513                 break;
514             }
515             case CLIPBOARD:
516             {
517                 // take shape from clipboard;
518                 Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get();
519                 if(cm->paste(desktop,true)){
520                     SPItem * pasted_clipboard = dc->selection->singleItem();
521                     dc->selection->toCurves(true);
522                     pasted_clipboard = dc->selection->singleItem();
523                     if(pasted_clipboard){
524                         Inkscape::XML::Node *pasted_clipboard_root = pasted_clipboard->getRepr();
525                         Inkscape::XML::Node *path = sp_repr_lookup_name(pasted_clipboard_root, "svg:path", -1); // unlimited search depth
526                         if ( path != nullptr ) {
527                             gchar const *svgd = path->attribute("d");
528                             dc->selection->remove(SP_OBJECT(pasted_clipboard));
529                             previous_shape_pathv =  sp_svg_read_pathv(svgd);
530                             previous_shape_pathv *= pasted_clipboard->transform;
531                             spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item);
532 
533                             shape = CLIPBOARD;
534                             shape_applied = true;
535                             pasted_clipboard->deleteObject();
536                         } else {
537                             shape = NONE;
538                         }
539                     } else {
540                         shape = NONE;
541                     }
542                 } else {
543                     shape = NONE;
544                 }
545                 break;
546             }
547             case BEND_CLIPBOARD:
548             {
549                 gchar const *svgd = item->getRepr()->attribute("d");
550                 if(bend_item && (SP_IS_SHAPE(bend_item) || SP_IS_GROUP(bend_item))){
551                     // If item is a SPRect, convert it to path first:
552                     if ( dynamic_cast<SPRect *>(bend_item) ) {
553                         if (desktop) {
554                             Inkscape::Selection *sel = desktop->getSelection();
555                             if ( sel && !sel->isEmpty() ) {
556                                 sel->clear();
557                                 sel->add(bend_item);
558                                 sel->toCurves();
559                                 bend_item = sel->singleItem();
560                             }
561                         }
562                     }
563                     bend_item->moveTo(item,false);
564                     bend_item->transform.setTranslation(Geom::Point());
565                     spdc_apply_bend_shape(svgd, dc, bend_item);
566                     dc->selection->add(SP_OBJECT(bend_item));
567 
568                     shape = BEND_CLIPBOARD;
569                 } else {
570                     bend_item = nullptr;
571                     shape = NONE;
572                 }
573                 break;
574             }
575             case LAST_APPLIED:
576             {
577                 if(previous_shape_type == CLIPBOARD){
578                     if(previous_shape_pathv.size() != 0){
579                         spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item);
580                         shape_applied = true;
581                         shape = CLIPBOARD;
582                     } else{
583                         shape = NONE;
584                     }
585                 } else {
586                     if(bend_item != nullptr && bend_item->getRepr() != nullptr){
587                         gchar const *svgd = item->getRepr()->attribute("d");
588                         dc->selection->add(SP_OBJECT(bend_item));
589                         dc->selection->duplicate();
590                         dc->selection->remove(SP_OBJECT(bend_item));
591                         bend_item = dc->selection->singleItem();
592                         if(bend_item){
593                             bend_item->moveTo(item,false);
594                             Geom::Coord expansion_X = bend_item->transform.expansionX();
595                             Geom::Coord expansion_Y = bend_item->transform.expansionY();
596                             bend_item->transform = Geom::Affine(1,0,0,1,0,0);
597                             bend_item->transform.setExpansionX(expansion_X);
598                             bend_item->transform.setExpansionY(expansion_Y);
599                             spdc_apply_bend_shape(svgd, dc, bend_item);
600                             dc->selection->add(SP_OBJECT(bend_item));
601 
602                             shape = BEND_CLIPBOARD;
603                         } else {
604                             shape = NONE;
605                         }
606                     } else {
607                         shape = NONE;
608                     }
609                 }
610                 break;
611             }
612             default:
613                 break;
614         }
615         previous_shape_type = shape;
616 
617         if (shape_applied) {
618             // apply original stroke color as fill and unset stroke; then return
619             SPCSSAttr *css = sp_repr_css_attr_new();
620             if (!strcmp(cfill, "none")) {
621                 sp_repr_css_set_property (css, "fill", cstroke);
622             } else {
623                 sp_repr_css_set_property (css, "fill", cfill);
624             }
625             sp_repr_css_set_property (css, "stroke", "none");
626             sp_desktop_apply_css_recursive(dc->white_item, css, true);
627             sp_repr_css_attr_unref(css);
628             return;
629         }
630         if (dc->waiting_LPE_type != INVALID_LPE) {
631             Effect::createAndApply(dc->waiting_LPE_type, dc->getDesktop()->getDocument(), item);
632             dc->waiting_LPE_type = INVALID_LPE;
633 
634             if (SP_IS_LPETOOL_CONTEXT(dc)) {
635                 // since a geometric LPE was applied, we switch back to "inactive" mode
636                 lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE);
637             }
638         }
639         if (SP_IS_PEN_CONTEXT(dc)) {
640             SP_PEN_CONTEXT(dc)->setPolylineMode();
641         }
642     }
643 }
644 
645 /*
646  * Selection handlers
647  */
648 
spdc_selection_changed(Inkscape::Selection * sel,FreehandBase * dc)649 static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc)
650 {
651     if (dc->attach) {
652         spdc_attach_selection(dc, sel);
653     }
654 }
655 
656 /* fixme: We have to ensure this is not delayed (Lauris) */
657 
spdc_selection_modified(Inkscape::Selection * sel,guint,FreehandBase * dc)658 static void spdc_selection_modified(Inkscape::Selection *sel, guint /*flags*/, FreehandBase *dc)
659 {
660     if (dc->attach) {
661         spdc_attach_selection(dc, sel);
662     }
663 }
664 
spdc_attach_selection(FreehandBase * dc,Inkscape::Selection *)665 static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection */*sel*/)
666 {
667     // We reset white and forget white/start/end anchors
668     spdc_reset_white(dc);
669     dc->sa = nullptr;
670     dc->ea = nullptr;
671 
672     SPItem *item = dc->selection ? dc->selection->singleItem() : nullptr;
673 
674     if ( item && SP_IS_PATH(item) ) {
675         // Create new white data
676         // Item
677         dc->white_item = item;
678 
679         // Curve list
680         // We keep it in desktop coordinates to eliminate calculation errors
681         auto path = static_cast<SPPath *>(item);
682         auto norm = SPCurve::copy(path->curveForEdit());
683         g_return_if_fail( norm != nullptr );
684         norm->transform((dc->white_item)->i2dt_affine());
685         dc->white_curves = norm->split();
686 
687         // Anchor list
688         for (auto const &c_smart_ptr : dc->white_curves) {
689             auto *c = c_smart_ptr.get();
690             g_return_if_fail( c->get_segment_count() > 0 );
691             if ( !c->is_closed() ) {
692                 SPDrawAnchor *a;
693                 a = sp_draw_anchor_new(dc, c, TRUE, *(c->first_point()));
694                 if (a)
695                     dc->white_anchors.push_back(a);
696                 a = sp_draw_anchor_new(dc, c, FALSE, *(c->last_point()));
697                 if (a)
698                     dc->white_anchors.push_back(a);
699             }
700         }
701         // fixme: recalculate active anchor?
702     }
703 }
704 
705 
spdc_endpoint_snap_rotation(ToolBase * const ec,Geom::Point & p,Geom::Point const & o,guint state)706 void spdc_endpoint_snap_rotation(ToolBase* const ec, Geom::Point &p, Geom::Point const &o,
707                                  guint state)
708 {
709     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
710     unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12));
711 
712     SnapManager &m = ec->getDesktop()->namedview->snap_manager;
713     m.setup(ec->getDesktop());
714 
715     bool snap_enabled = m.snapprefs.getSnapEnabledGlobally();
716     if (state & GDK_SHIFT_MASK) {
717         // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular
718         // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable
719         // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round
720         // to the nearest angle increment)
721         m.snapprefs.setSnapEnabledGlobally(false);
722     }
723 
724     Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), std::optional<Geom::Point>(), o, snaps);
725     p = dummy.getPoint();
726 
727     if (state & GDK_SHIFT_MASK) {
728         m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting
729     }
730 
731     m.unSetup();
732 }
733 
734 
spdc_endpoint_snap_free(ToolBase * const ec,Geom::Point & p,std::optional<Geom::Point> & start_of_line,guint const)735 void spdc_endpoint_snap_free(ToolBase* const ec, Geom::Point& p, std::optional<Geom::Point> &start_of_line, guint const /*state*/)
736 {
737     const SPDesktop *dt = ec->getDesktop();
738     SnapManager &m = dt->namedview->snap_manager;
739     Inkscape::Selection *selection = dt->getSelection();
740 
741     // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
742     // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
743 
744     m.setup(dt, true, selection->singleItem());
745     Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
746     if (start_of_line) {
747         scp.addOrigin(*start_of_line);
748     }
749 
750     Inkscape::SnappedPoint sp = m.freeSnap(scp);
751     p = sp.getPoint();
752 
753     m.unSetup();
754 }
755 
spdc_concat_colors_and_flush(FreehandBase * dc,gboolean forceclosed)756 void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed)
757 {
758     // Concat RBG
759     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
760 
761     // Green
762     auto c = std::make_unique<SPCurve>();
763     std::swap(c, dc->green_curve);
764     for (auto path : dc->green_bpaths) {
765         delete path;
766     }
767     dc->green_bpaths.clear();
768 
769     // Blue
770     c->append_continuous(*dc->blue_curve);
771     dc->blue_curve->reset();
772     dc->blue_bpath->set_bpath(nullptr);
773 
774     // Red
775     if (dc->red_curve_is_valid) {
776         c->append_continuous(*(dc->red_curve));
777     }
778     dc->red_curve->reset();
779     dc->red_bpath->set_bpath(nullptr);
780 
781     if (c->is_empty()) {
782         return;
783     }
784 
785     // Step A - test, whether we ended on green anchor
786     if ( (forceclosed &&
787          (!dc->sa || (dc->sa && dc->sa->curve->is_empty()))) ||
788          ( dc->green_anchor && dc->green_anchor->active))
789     {
790         // We hit green anchor, closing Green-Blue-Red
791         dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
792         c->closepath_current();
793         // Closed path, just flush
794         spdc_flush_white(dc, c.get());
795         return;
796     }
797 
798     // Step B - both start and end anchored to same curve
799     if ( dc->sa && dc->ea
800          && ( dc->sa->curve == dc->ea->curve )
801          && ( ( dc->sa != dc->ea )
802               || dc->sa->curve->is_closed() ) )
803     {
804         // We hit bot start and end of single curve, closing paths
805         dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path."));
806         dc->sa_overwrited->append_continuous(*c);
807         dc->sa_overwrited->closepath_current();
808         if (!dc->white_curves.empty()) {
809             dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), dc->sa->curve));
810         }
811         dc->white_curves.push_back(std::move(dc->sa_overwrited));
812         spdc_flush_white(dc, nullptr);
813         return;
814     }
815     // Step C - test start
816     if (dc->sa) {
817         if (!dc->white_curves.empty()) {
818             dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), dc->sa->curve));
819         }
820         dc->sa_overwrited->append_continuous(*c);
821         c = std::move(dc->sa_overwrited);
822     } else /* Step D - test end */ if (dc->ea) {
823         auto e = std::move(dc->ea->curve);
824         if (!dc->white_curves.empty()) {
825             dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), e));
826         }
827         if (!dc->ea->start) {
828             e = e->create_reverse();
829         }
830         if(prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
831             prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2){
832                 e = e->create_reverse();
833                 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*e->last_segment());
834                 if(cubic){
835                     auto lastSeg = std::make_unique<SPCurve>();
836                     lastSeg->moveto((*cubic)[0]);
837                     lastSeg->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
838                     if( e->get_segment_count() == 1){
839                         e = std::move(lastSeg);
840                     }else{
841                         //we eliminate the last segment
842                         e->backspace();
843                         //and we add it again with the recreation
844                         e->append_continuous(*lastSeg);
845                     }
846                 }
847                 e = e->create_reverse();
848         }
849         c->append_continuous(*e);
850     }
851     if (forceclosed)
852     {
853         dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
854         c->closepath_current();
855     }
856     spdc_flush_white(dc, c.get());
857 }
858 
spdc_flush_white(FreehandBase * dc,SPCurve * gc)859 static void spdc_flush_white(FreehandBase *dc, SPCurve *gc)
860 {
861     std::unique_ptr<SPCurve> c;
862 
863     if (! dc->white_curves.empty()) {
864         g_assert(dc->white_item);
865 
866         // c = concat(white_curves)
867         c = std::make_unique<SPCurve>();
868         for (auto const &wc : dc->white_curves) {
869             c->append(*wc);
870         }
871 
872         dc->white_curves.clear();
873         if (gc) {
874             c->append(*gc);
875         }
876     } else if (gc) {
877         c = gc->ref();
878     } else {
879         return;
880     }
881 
882     SPDesktop *desktop = dc->getDesktop();
883     SPDocument *doc = desktop->getDocument();
884     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
885 
886     // Now we have to go back to item coordinates at last
887     c->transform( dc->white_item
888                 ? (dc->white_item)->dt2i_affine()
889                 :  desktop->dt2doc() );
890 
891     if ( c && !c->is_empty() ) {
892         // We actually have something to write
893 
894         bool has_lpe = false;
895         Inkscape::XML::Node *repr;
896 
897         if (dc->white_item) {
898             repr = dc->white_item->getRepr();
899             has_lpe = SP_LPE_ITEM(dc->white_item)->hasPathEffectRecursive();
900         } else {
901             repr = xml_doc->createElement("svg:path");
902             // Set style
903             sp_desktop_apply_style_tool(desktop, repr, tool_name(dc).data(), false);
904         }
905 
906         auto str = sp_svg_write_path(c->get_pathvector());
907         if (has_lpe)
908             repr->setAttribute("inkscape:original-d", str);
909         else
910             repr->setAttribute("d", str);
911 
912         if (SP_IS_PENCIL_CONTEXT(dc) && dc->tablet_enabled) {
913             if (!dc->white_item) {
914                 dc->white_item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
915             }
916             spdc_check_for_and_apply_waiting_LPE(dc, dc->white_item, c.get(), false);
917         }
918         if (!dc->white_item) {
919             // Attach repr
920             SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
921             dc->white_item = item;
922             //Bend needs the transforms applied after, Other effects best before
923             spdc_check_for_and_apply_waiting_LPE(dc, item, c.get(), true);
924             Inkscape::GC::release(repr);
925             item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
926             item->updateRepr();
927             item->doWriteTransform(item->transform, nullptr, true);
928             spdc_check_for_and_apply_waiting_LPE(dc, item, c.get(), false);
929             if(previous_shape_type == BEND_CLIPBOARD){
930                 repr->parent()->removeChild(repr);
931             } else {
932                 dc->selection->set(repr);
933             }
934         }
935         DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL,
936                          _("Draw path"));
937 
938         // When quickly drawing several subpaths with Shift, the next subpath may be finished and
939         // flushed before the selection_modified signal is fired by the previous change, which
940         // results in the tool losing all of the selected path's curve except that last subpath. To
941         // fix this, we force the selection_modified callback now, to make sure the tool's curve is
942         // in sync immediately.
943         spdc_selection_modified(desktop->getSelection(), 0, dc);
944     }
945 
946     // Flush pending updates
947     doc->ensureUpToDate();
948 }
949 
spdc_test_inside(FreehandBase * dc,Geom::Point p)950 SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p)
951 {
952     SPDrawAnchor *active = nullptr;
953 
954     // Test green anchor
955     if (dc->green_anchor) {
956         active = sp_draw_anchor_test(dc->green_anchor, p, TRUE);
957     }
958 
959     for (auto i:dc->white_anchors) {
960         SPDrawAnchor *na = sp_draw_anchor_test(i, p, !active);
961         if ( !active && na ) {
962             active = na;
963         }
964     }
965     return active;
966 }
967 
spdc_reset_white(FreehandBase * dc)968 static void spdc_reset_white(FreehandBase *dc)
969 {
970     if (dc->white_item) {
971         // We do not hold refcount
972         dc->white_item = nullptr;
973     }
974     dc->white_curves.clear();
975     for (auto i:dc->white_anchors)
976         sp_draw_anchor_destroy(i);
977     dc->white_anchors.clear();
978 }
979 
spdc_free_colors(FreehandBase * dc)980 static void spdc_free_colors(FreehandBase *dc)
981 {
982     // Red
983     if (dc->red_bpath) {
984         delete dc->red_bpath;
985         dc->red_bpath = nullptr;
986     }
987     dc->red_curve.reset();
988 
989     // Blue
990     if (dc->blue_bpath) {
991         delete dc->blue_bpath;
992         dc->blue_bpath = nullptr;
993     }
994     dc->blue_curve.reset();
995 
996     // Overwrite start anchor curve
997     dc->sa_overwrited.reset();
998     // Green
999     for (auto path : dc->green_bpaths) {
1000         delete path;
1001     }
1002     dc->green_bpaths.clear();
1003     dc->green_curve.reset();
1004     if (dc->green_anchor) {
1005         dc->green_anchor = sp_draw_anchor_destroy(dc->green_anchor);
1006     }
1007 
1008     // White
1009     if (dc->white_item) {
1010         // We do not hold refcount
1011         dc->white_item = nullptr;
1012     }
1013     dc->white_curves.clear();
1014     for (auto i : dc->white_anchors)
1015         sp_draw_anchor_destroy(i);
1016     dc->white_anchors.clear();
1017 }
1018 
spdc_create_single_dot(ToolBase * ec,Geom::Point const & pt,char const * tool,guint event_state)1019 void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state) {
1020     g_return_if_fail(!strcmp(tool, "/tools/freehand/pen") || !strcmp(tool, "/tools/freehand/pencil")
1021             || !strcmp(tool, "/tools/calligraphic") );
1022     Glib::ustring tool_path = tool;
1023 
1024     SPDesktop *desktop = ec->getDesktop();
1025     Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
1026     Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
1027     repr->setAttribute("sodipodi:type", "arc");
1028     SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
1029     item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
1030     Inkscape::GC::release(repr);
1031 
1032     // apply the tool's current style
1033     sp_desktop_apply_style_tool(desktop, repr, tool, false);
1034 
1035     // find out stroke width (TODO: is there an easier way??)
1036     double stroke_width = 3.0;
1037     gchar const *style_str = repr->attribute("style");
1038     if (style_str) {
1039         SPStyle style(desktop->doc());
1040         style.mergeString(style_str);
1041         stroke_width = style.stroke_width.computed;
1042     }
1043 
1044     // unset stroke and set fill color to former stroke color
1045     gchar * str;
1046     str = strcmp(tool, "/tools/calligraphic") ? g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, tool, false) >> 8)
1047         : g_strdup_printf("fill:#%06x;stroke:#%06x;", sp_desktop_get_color_tool(desktop, tool, true) >> 8, sp_desktop_get_color_tool(desktop, tool, false) >> 8);
1048     repr->setAttribute("style", str);
1049     g_free(str);
1050 
1051     // put the circle where the mouse click occurred and set the diameter to the
1052     // current stroke width, multiplied by the amount specified in the preferences
1053     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1054 
1055     Geom::Affine const i2d (item->i2dt_affine ());
1056     Geom::Point pp = pt * i2d.inverse();
1057 
1058     double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0);
1059     if (!strcmp(tool, "/tools/calligraphic"))
1060         rad = 0.0333 * prefs->getDouble(tool_path + "/width", 3.0) / desktop->current_zoom() / desktop->getDocument()->getDocumentScale()[Geom::X];
1061     if (event_state & GDK_MOD1_MASK) {
1062         // TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size
1063         // as specified in prefs. Very simple, but it might be sufficient in practice. If not,
1064         // we need to devise something more sophisticated.
1065         double s = g_random_double_range(-0.5, 0.5);
1066         rad *= (1 + s);
1067     }
1068     if (event_state & GDK_SHIFT_MASK) {
1069         // double the point size
1070         rad *= 2;
1071     }
1072 
1073     sp_repr_set_svg_double (repr, "sodipodi:cx", pp[Geom::X]);
1074     sp_repr_set_svg_double (repr, "sodipodi:cy", pp[Geom::Y]);
1075     sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width);
1076     sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width);
1077     item->updateRepr();
1078     item->doWriteTransform(item->transform, nullptr, true);
1079 
1080     desktop->getSelection()->set(item);
1081 
1082     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot"));
1083     DocumentUndo::done(desktop->getDocument(), SP_VERB_NONE, _("Create single dot"));
1084 }
1085 
1086 }
1087 }
1088 }
1089 
1090 /*
1091   Local Variables:
1092   mode:c++
1093   c-file-style:"stroustrup"
1094   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1095   indent-tabs-mode:nil
1096   fill-column:99
1097   End:
1098 */
1099 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1100