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