Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
spray-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Spray Tool
4 *
5 * Authors:
6 * Pierre-Antoine MARC
7 * Pierre CACLIN
8 * Aurel-Aimé MARMION
9 * Julien LERAY
10 * Benoît LAVORATA
11 * Vincent MONTAGNE
12 * Pierre BARBRY-BLOT
13 * Steren GIANNINI (steren.giannini@gmail.com)
14 * Jon A. Cruz <jon@joncruz.org>
15 * Abhishek Sharma
16 * Jabiertxo Arraiza <jabier.arraiza@marker.es>
17 * Adrian Boguszewski
18 *
19 * Copyright (C) 2009 authors
20 *
21 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
22 */
23
24#include "spray-tool.h"
25
26#include <vector>
27
28#include <gdk/gdkkeysyms.h>
29#include <glibmm/i18n.h>
30
31#include <2geom/circle.h>
32
33#include "colors/utils.h"
34#include "context-fns.h"
35#include "desktop-style.h"
36#include "desktop.h"
37#include "document-undo.h"
38#include "document.h"
39#include "message-context.h"
40#include "selection.h"
41
42#include "display/curve.h"
43#include "display/drawing.h"
46
47#include "object/box3d.h"
48#include "object/sp-shape.h"
49#include "object/sp-use.h"
50
51#include "ui/icon-names.h"
54
56
57#define DDC_RED_RGBA 0xff0000ff
58#define DYNA_MIN_WIDTH 1.0e-6
59
60// Disabled in 0.91 because of Bug #1274831 (crash, spraying an object
61// with the mode: spray object in single path)
62// Please enable again when working on 1.0
63#define ENABLE_SPRAY_MODE_SINGLE_PATH
64
65namespace Inkscape::UI::Tools {
66
67enum {
75 PICK_L
76};
77
83inline double NormalDistribution(double mu, double sigma)
84{
85 // use Box Muller's algorithm
86 return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) );
87}
88
93{
94 auto const translate = Geom::Translate(center);
95 return translate.inverse() * affine * translate;
96}
97
98static void transform_keep_center(SPItem *item, Geom::Affine const &affine, Geom::Point const &center)
99{
100 // This order allows us to avoid more reprUpdates than needed
101 item->set_i2d_affine(item->i2dt_affine() * affine);
102 item->updateCenterIfSet(center);
104}
105
106static void get_paths(SPItem * item, Geom::PathVector &res, bool root = true) {
107 if (auto bbox = item->documentVisualBounds()) {
108 auto clone = cast<SPUse>(item);
109 if (auto grp = cast<SPGroup>(item)) {
110 for (auto ig : grp->item_list()) {
111 get_paths(cast<SPItem>(ig), res, false);
112 }
113 } else if (auto shape = cast<SPShape>(item)) {
114 Geom::Affine trans = item->i2doc_affine();
115 for (auto path : shape->curve()->get_pathvector()) {
116 path *= trans;
117 res.insert(res.end(), path);
118 }
119 } else if (clone && !root) {
120 get_paths(clone->trueOriginal(), res, false);
121 }
122 if (root) {
123 if (clone) {
124 get_paths(clone->trueOriginal(), res, false);
125 res *= clone->trueOriginal()->transform.inverse();
126 res *= clone->get_root_transform();
127 bbox = res.boundsFast();
128 }
129 if (bbox) {
130 res *= Geom::Translate(bbox->midpoint()).inverse();
131 }
132 }
133 }
134}
135
137 : ToolBase(desktop, "/tools/spray", "spray.svg", false)
138 , pressure(TC_DEFAULT_PRESSURE)
139{
140 dilate_area = make_canvasitem<CanvasItemBpath>(desktop->getCanvasControls());
141 dilate_area->set_stroke(0xff9900ff);
142 dilate_area->set_fill(0x0, SP_WIND_RULE_EVENODD);
143 dilate_area->set_visible(false);
144
145 shapes_area = make_canvasitem<CanvasItemBpath>(desktop->getCanvasControls());
146 shapes_area->set_stroke(0x333333ff);
147 shapes_area->set_fill(0x0, SP_WIND_RULE_EVENODD);
148 shapes_area->set_visible(false);
149
151 prefs->setBool("/dialogs/clonetiler/dotrace", false);
152 if (prefs->getBool("/tools/spray/selcue")) {
153 this->enableSelectionCue();
154 }
155 if (prefs->getBool("/tools/spray/gradientdrag")) {
156 this->enableGrDrag();
157 }
158 sp_event_context_read(this, "distrib");
159 sp_event_context_read(this, "width");
160 sp_event_context_read(this, "ratio");
161 sp_event_context_read(this, "tilt");
162 sp_event_context_read(this, "rotation_variation");
163 sp_event_context_read(this, "scale_variation");
164 sp_event_context_read(this, "mode");
165 sp_event_context_read(this, "population");
166 sp_event_context_read(this, "mean");
167 sp_event_context_read(this, "standard_deviation");
168 sp_event_context_read(this, "usepressurewidth");
169 sp_event_context_read(this, "usepressurepopulation");
170 sp_event_context_read(this, "usepressurescale");
171 sp_event_context_read(this, "Scale");
172 sp_event_context_read(this, "offset");
173 sp_event_context_read(this, "picker");
174 sp_event_context_read(this, "pick_center");
175 sp_event_context_read(this, "pick_inverse_value");
176 sp_event_context_read(this, "pick_fill");
177 sp_event_context_read(this, "pick_stroke");
178 sp_event_context_read(this, "pick_no_overlap");
179 sp_event_context_read(this, "over_no_transparent");
180 sp_event_context_read(this, "over_transparent");
181 sp_event_context_read(this, "no_overlap");
182
183 // Construct the object_set we'll be using for this spray operation
184 auto const selected_objects = _desktop->getSelection()->objects();
185 object_set.add(selected_objects.begin(), selected_objects.end());
186}
187
189 this->enableGrDrag(false);
190}
191
192void SprayTool::update_cursor(bool /*with_shift*/) {
193 guint num = 0;
194 gchar *sel_message = nullptr;
195
196 if (!object_set.isEmpty()) {
197 num = object_set.size();
198 sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
199 } else {
200 sel_message = g_strdup_printf("%s", _("<b>Nothing</b> selected"));
201 }
202
203 switch (this->mode) {
204 case SPRAY_MODE_COPY:
205 this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>copies</b> of the initial selection. Right-click + move to update single click item."), sel_message);
206 break;
207 case SPRAY_MODE_CLONE:
208 this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>clones</b> of the initial selection. Right-click + move to update single click item."), sel_message);
209 break;
211 this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray into a <b>single path</b>. Right-click + move to update single click item."), sel_message);
212 break;
213 default:
214 break;
215 }
216 g_free(sel_message);
217}
218
219
222 do_trace = prefs->getBool("/dialogs/clonetiler/dotrace", false);
223 pick = prefs->getInt("/dialogs/clonetiler/pick");
224 pick_to_size = prefs->getBool("/dialogs/clonetiler/pick_to_size", false);
225 pick_to_presence = prefs->getBool("/dialogs/clonetiler/pick_to_presence", false);
226 pick_to_color = prefs->getBool("/dialogs/clonetiler/pick_to_color", false);
227 pick_to_opacity = prefs->getBool("/dialogs/clonetiler/pick_to_opacity", false);
228 rand_picked = 0.01 * prefs->getDoubleLimited("/dialogs/clonetiler/rand_picked", 0, 0, 100);
229 invert_picked = prefs->getBool("/dialogs/clonetiler/invert_picked", false);
230 gamma_picked = prefs->getDoubleLimited("/dialogs/clonetiler/gamma_picked", 0, -10, 10);
231}
232
234 Glib::ustring path = val.getEntryName();
235
236 if (path == "mode") {
237 this->mode = val.getInt();
238 this->update_cursor(false);
239 } else if (path == "width") {
240 this->width = 0.01 * CLAMP(val.getInt(10), 1, 100);
241 } else if (path == "usepressurewidth") {
242 this->usepressurewidth = val.getBool();
243 } else if (path == "usepressurepopulation") {
244 this->usepressurepopulation = val.getBool();
245 } else if (path == "usepressurescale") {
246 this->usepressurescale = val.getBool();
247 } else if (path == "population") {
248 this->population = 0.01 * CLAMP(val.getInt(10), 1, 100);
249 } else if (path == "rotation_variation") {
250 this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0);
251 } else if (path == "scale_variation") {
252 this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0);
253 } else if (path == "standard_deviation") {
254 this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100);
255 } else if (path == "mean") {
256 this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100);
257// Not implemented in the toolbar and preferences yet
258 } else if (path == "distribution") {
259 this->distrib = val.getInt(1);
260 } else if (path == "tilt") {
261 this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0);
262 } else if (path == "ratio") {
263 this->ratio = CLAMP(val.getDouble(), 0.0, 0.9);
264 } else if (path == "offset") {
265 this->offset = val.getDoubleLimited(100.0, 0, 1000.0);
266 } else if (path == "pick_center") {
267 this->pick_center = val.getBool(true);
268 } else if (path == "pick_inverse_value") {
269 this->pick_inverse_value = val.getBool(false);
270 } else if (path == "pick_fill") {
271 this->pick_fill = val.getBool(false);
272 } else if (path == "pick_stroke") {
273 this->pick_stroke = val.getBool(false);
274 } else if (path == "pick_no_overlap") {
275 this->pick_no_overlap = val.getBool(false);
276 } else if (path == "over_no_transparent") {
277 this->over_no_transparent = val.getBool(true);
278 } else if (path == "over_transparent") {
279 this->over_transparent = val.getBool(true);
280 } else if (path == "no_overlap") {
281 this->no_overlap = val.getBool(false);
282 } else if (path == "picker") {
283 this->picker = val.getBool(false);
284 }
285}
286
287static void sp_spray_extinput(SprayTool *tc, ExtendedInput const &ext)
288{
289 if (ext.pressure) {
290 tc->pressure = CLAMP(*ext.pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
291 } else {
292 tc->pressure = TC_DEFAULT_PRESSURE;
293 }
294}
295
296static double get_width(SprayTool *tc)
297{
298 double pressure = (tc->usepressurewidth? tc->pressure / TC_DEFAULT_PRESSURE : 1);
299 return pressure * tc->width;
300}
301
302static double get_dilate_radius(SprayTool *tc)
303{
304 return 250 * get_width(tc) /tc->getDesktop()->current_zoom();
305}
306
307static double get_path_mean(SprayTool *tc)
308{
309 return tc->mean;
310}
311
313{
314 return tc->standard_deviation;
315}
316
317static double get_population(SprayTool *tc)
318{
319 double pressure = (tc->usepressurepopulation? tc->pressure / TC_DEFAULT_PRESSURE : 1);
320 return pressure * tc->population;
321}
322
323static double get_pressure(SprayTool *tc)
324{
325 double pressure = tc->pressure / TC_DEFAULT_PRESSURE;
326 return pressure;
327}
328
329static double get_move_mean(SprayTool *tc)
330{
331 return tc->mean;
332}
333
335{
336 return tc->standard_deviation;
337}
338
348static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/)
349{
350 // angle is taken from an uniform distribution
351 angle = g_random_double_range(0, M_PI*2.0);
352
353 // radius is taken from a Normal Distribution
354 double radius_temp =-1;
355 while(!((radius_temp >= 0) && (radius_temp <=1 )))
356 {
357 radius_temp = NormalDistribution(a, s);
358 }
359 // Because we are in polar coordinates, a special treatment has to be done to the radius.
360 // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to
361 // be uniformily distributed on the disk (more at the center and less at the boundary).
362 // We counter this effect with a 0.5 exponent. This is empiric.
363 radius = pow(radius_temp, 0.5);
364
365}
366
368 path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr).inverse();
369 path *= item->transform.inverse();
370 Geom::Affine dt2p;
371 if (item->parent) {
372 dt2p = static_cast<SPItem *>(item->parent)->i2dt_affine().inverse();
373 } else {
374 dt2p = item->document->dt2doc();
375 }
376 Geom::Affine i2dt = item->i2dt_affine() * Geom::Translate(center).inverse() * affine * Geom::Translate(center);
377 path *= i2dt * dt2p;
378 path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr);
379}
380
385double randomize01(double val, double rand)
386{
387 double base = MIN (val - rand, 1 - 2*rand);
388 if (base < 0) {
389 base = 0;
390 }
391 val = base + g_random_double_range (0, MIN (2 * rand, 1 - base));
392 return CLAMP(val, 0, 1); // this should be unnecessary with the above provisions, but just in case...
393}
394
396{
397 Inkscape::CanvasItemDrawing *canvas_item_drawing = desktop->getCanvasDrawing();
398 Inkscape::Drawing *drawing = canvas_item_drawing->get_drawing();
399
400 // Get average color.
401 auto avg = drawing->averageColor(area);
402
403 //this can fix the bug #1511998 if confirmed
404 if (avg.getOpacity() < 1e-6) {
405 avg.set(0, 1.0);
406 avg.set(1, 1.0);
407 avg.set(2, 1.0);
408 }
409
410 return avg.toRGBA();
411}
412
413static void showHidden(std::vector<SPItem *> items_down){
414 for (auto item_hidden : items_down) {
415 item_hidden->setHidden(false);
416 item_hidden->updateRepr();
417 }
418}
419//todo: maybe move same parameter to preferences
422 SPItem *item,
423 Geom::OptRect bbox,
424 Geom::Point &move,
425 Geom::Point center,
426 gint mode,
427 double angle,
428 double &_scale,
429 double scale,
430 bool picker,
431 bool pick_center,
432 bool pick_inverse_value,
433 bool pick_fill,
434 bool pick_stroke,
435 bool pick_no_overlap,
436 bool over_no_transparent,
437 bool over_transparent,
438 bool no_overlap,
439 double offset,
440 SPCSSAttr *css,
441 bool trace_scale,
442 int pick,
443 bool do_trace,
444 bool single_click,
445 bool pick_to_size,
446 bool pick_to_presence,
447 bool pick_to_color,
448 bool pick_to_opacity,
449 bool invert_picked,
450 double gamma_picked ,
451 double rand_picked)
452{
453 if (set->isEmpty()) {
454 return false;
455 }
456 SPDocument *doc = item->document;
457 double width = bbox->width();
458 double height = bbox->height();
459 double offset_width = (offset * width)/100.0 - (width);
460 if(offset_width < 0 ){
461 offset_width = 0;
462 }
463 double offset_height = (offset * height)/100.0 - (height);
464 if(offset_height < 0 ){
465 offset_height = 0;
466 }
467 if(picker && pick_to_size && !trace_scale && do_trace){
468 _scale = 0.1;
469 }
470 Geom::OptRect bbox_procesed = Geom::Rect(Geom::Point(bbox->left() - offset_width, bbox->top() - offset_height),Geom::Point(bbox->right() + offset_width, bbox->bottom() + offset_height));
471 Geom::Path path;
472 path.start(Geom::Point(bbox_procesed->left(), bbox_procesed->top()));
473 path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->top()));
474 path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->bottom()));
475 path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->left(), bbox_procesed->bottom()));
476 path.close(true);
477 sp_spray_transform_path(item, path, Geom::Scale(_scale), center);
479 sp_spray_transform_path(item, path, Geom::Rotate(angle), center);
480 path *= Geom::Translate(move);
481 path *= desktop->doc2dt();
482 bbox_procesed = path.boundsFast();
483 double bbox_left_main = bbox_procesed->left();
484 double bbox_right_main = bbox_procesed->right();
485 double bbox_top_main = bbox_procesed->top();
486 double bbox_bottom_main = bbox_procesed->bottom();
487 double width_transformed = bbox_procesed->width();
488 double height_transformed = bbox_procesed->height();
489 Geom::Point mid_point = desktop->d2w(bbox_procesed->midpoint());
490 Geom::IntRect area = Geom::IntRect::from_xywh(floor(mid_point[Geom::X]), floor(mid_point[Geom::Y]), 1, 1);
491 guint32 rgba = getPickerData(area, desktop);
492 guint32 rgba2 = 0xffffff00;
493 Geom::Rect rect_sprayed(desktop->d2w(Geom::Point(bbox_left_main,bbox_top_main)), desktop->d2w(Geom::Point(bbox_right_main,bbox_bottom_main)));
494 if (!rect_sprayed.hasZeroArea()) {
495 rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
496 }
497 if(pick_no_overlap) {
498 if(rgba != rgba2) {
499 if(mode != SPRAY_MODE_ERASER) {
500 return false;
501 }
502 }
503 }
504 if(!pick_center) {
505 rgba = rgba2;
506 }
507 if(!over_transparent && (SP_RGBA32_A_F(rgba) == 0 || SP_RGBA32_A_F(rgba) < 1e-6)) {
508 if(mode != SPRAY_MODE_ERASER) {
509 return false;
510 }
511 }
512 if(!over_no_transparent && SP_RGBA32_A_F(rgba) > 0) {
513 if(mode != SPRAY_MODE_ERASER) {
514 return false;
515 }
516 }
517 if(offset < 100 ) {
518 offset_width = ((99.0 - offset) * width_transformed)/100.0 - width_transformed;
519 offset_height = ((99.0 - offset) * height_transformed)/100.0 - height_transformed;
520 } else {
521 offset_width = 0;
522 offset_height = 0;
523 }
524 std::vector<SPItem*> items_down = desktop->getDocument()->getItemsPartiallyInBox(desktop->dkey, *bbox_procesed);
525 std::vector<SPItem*> items_down_erased;
526 for (std::vector<SPItem*>::const_iterator i=items_down.begin(); i!=items_down.end(); ++i) {
527 SPItem *item_down = *i;
528 Geom::OptRect bbox_down = item_down->documentVisualBounds();
529 double bbox_left = bbox_down->left();
530 double bbox_top = bbox_down->top();
531 gchar const * item_down_sharp = g_strdup_printf("#%s", item_down->getId());
532 items_down_erased.push_back(item_down);
533 for (auto item_selected : set->items()) {
534 gchar const * spray_origin;
535 if(!item_selected->getAttribute("inkscape:spray-origin")){
536 spray_origin = g_strdup_printf("#%s", item_selected->getId());
537 } else {
538 spray_origin = item_selected->getAttribute("inkscape:spray-origin");
539 }
540 if(strcmp(item_down_sharp, spray_origin) == 0 ||
541 (item_down->getAttribute("inkscape:spray-origin") &&
542 strcmp(item_down->getAttribute("inkscape:spray-origin"),spray_origin) == 0 ))
543 {
544 if(mode == SPRAY_MODE_ERASER) {
545 if(strcmp(item_down_sharp, spray_origin) != 0 && !set->includes(item_down) ){
546 item_down->deleteObject();
547 items_down_erased.pop_back();
548 break;
549 }
550 } else if(no_overlap) {
551 if(!(offset_width < 0 && offset_height < 0 && std::abs(bbox_left - bbox_left_main) > std::abs(offset_width) &&
552 std::abs(bbox_top - bbox_top_main) > std::abs(offset_height))){
553 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
554 showHidden(items_down);
555 }
556 return false;
557 }
558 } else if(picker || over_transparent || over_no_transparent) {
559 item_down->setHidden(true);
560 item_down->updateRepr();
561 }
562 }
563 }
564 }
565 if(mode == SPRAY_MODE_ERASER){
566 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
567 showHidden(items_down_erased);
568 }
569 return false;
570 }
571 if(picker || over_transparent || over_no_transparent){
572 if(!no_overlap){
573 doc->ensureUpToDate();
574 rgba = getPickerData(area, desktop);
575 if (!rect_sprayed.hasZeroArea()) {
576 rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
577 }
578 }
579 if(pick_no_overlap){
580 if(rgba != rgba2){
581 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
582 showHidden(items_down);
583 }
584 return false;
585 }
586 }
587 if(!pick_center){
588 rgba = rgba2;
589 }
590 double opacity = 1.0;
591 gchar color_string[32]; *color_string = 0;
592 auto color = Colors::Color(rgba);
593 bool invisible = color.getOpacity() < 1e-6;
594
595 if(!over_transparent && invisible){
596 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
597 showHidden(items_down);
598 }
599 return false;
600 }
601 if(!over_no_transparent && !invisible){
602 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
603 showHidden(items_down);
604 }
605 return false;
606 }
607
608 if(picker && do_trace){
609 auto hsl = *color.converted(Colors::Space::Type::HSL);
610
611 gdouble val = 0;
612 switch (pick) {
613 case PICK_COLOR:
614 val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max
615 break;
616 case PICK_OPACITY:
617 val = color.getOpacity();
618 break;
619 case PICK_R:
620 val = color[0];
621 break;
622 case PICK_G:
623 val = color[1];
624 break;
625 case PICK_B:
626 val = color[2];
627 break;
628 case PICK_H:
629 val = hsl[0];
630 break;
631 case PICK_S:
632 val = hsl[1];
633 break;
634 case PICK_L:
635 val = 1 - hsl[2];
636 break;
637 default:
638 break;
639 }
640
641 if (rand_picked > 0) {
642 val = randomize01 (val, rand_picked);
643 for (auto i = 0; i < 3; i++) {
644 color.set(i, randomize01(color[i], rand_picked));
645 }
646 }
647
648 if (gamma_picked != 0) {
649 double power;
650 if (gamma_picked > 0)
651 power = 1/(1 + fabs(gamma_picked));
652 else
653 power = 1 + fabs(gamma_picked);
654
655 val = pow (val, power);
656 for (auto i = 0; i < 3; i++) {
657 color.set(i, pow(color[i], (double)power));
658 }
659 }
660
661 if (invert_picked) {
662 val = 1 - val;
663 color.invert();
664 }
665
666 val = CLAMP (val, 0, 1);
667 color.normalize();
668
669 if (pick_to_size) {
670 if(!trace_scale){
671 if(pick_inverse_value) {
672 _scale = 1.0 - val;
673 } else {
674 _scale = val;
675 }
676 if(_scale == 0.0) {
677 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
678 showHidden(items_down);
679 }
680 return false;
681 }
682 if(!fit_item(desktop
683 , set
684 , item
685 , bbox
686 , move
687 , center
688 , mode
689 , angle
690 , _scale
691 , scale
692 , picker
693 , pick_center
694 , pick_inverse_value
695 , pick_fill
696 , pick_stroke
697 , pick_no_overlap
698 , over_no_transparent
699 , over_transparent
700 , no_overlap
701 , offset
702 , css
703 , true
704 , pick
705 , do_trace
706 , single_click
707 , pick_to_size
708 , pick_to_presence
709 , pick_to_color
710 , pick_to_opacity
711 , invert_picked
712 , gamma_picked
713 , rand_picked)
714 )
715 {
716 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
717 showHidden(items_down);
718 }
719 return false;
720 }
721 }
722 }
723
724 if (pick_to_opacity) {
725 if(pick_inverse_value) {
726 opacity *= 1.0 - val;
727 } else {
728 opacity *= val;
729 }
730 std::stringstream opacity_str;
731 opacity_str.imbue(std::locale::classic());
732 opacity_str << opacity;
733 sp_repr_css_set_property(css, "opacity", opacity_str.str().c_str());
734 }
735 if (pick_to_presence) {
736 if (g_random_double_range (0, 1) > val) {
737 //Hiding the element is a way to retain original
738 //behaviour of tiled clones for presence option.
739 sp_repr_css_set_property(css, "opacity", "0");
740 }
741 }
742 if (pick_to_color) {
743 sp_repr_css_set_property_string(css, pick_fill ? "fill" : "stroke", Inkscape::Colors::rgba_to_hex(rgba));
744 }
745 if (opacity < 1e-6) { // invisibly transparent, skip
746 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
747 showHidden(items_down);
748 }
749 return false;
750 }
751 }
752 if(!do_trace){
753 if(!pick_center){
754 rgba = rgba2;
755 }
756 auto color = Colors::Color(rgba);
757 if (pick_inverse_value) {
758 color.invert();
759 }
760 sp_repr_css_set_property_string(css, pick_fill ? "fill" : "stroke", color.toString());
761 }
762 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
763 showHidden(items_down);
764 }
765 }
766 return true;
767}
768
771 SPItem *item,
772 SPItem *&single_path_output,
773 Geom::Point p,
774 Geom::Point /*vector*/,
775 gint mode,
776 double radius,
777 double population,
778 double &scale,
779 double scale_variation,
780 bool /*reverse*/,
781 double mean,
782 double standard_deviation,
783 double ratio,
784 double tilt,
785 double rotation_variation,
786 gint _distrib,
787 bool no_overlap,
788 bool picker,
789 bool pick_center,
790 bool pick_inverse_value,
791 bool pick_fill,
792 bool pick_stroke,
793 bool pick_no_overlap,
794 bool over_no_transparent,
795 bool over_transparent,
796 double offset,
797 bool usepressurescale,
798 double pressure,
799 int pick,
800 bool do_trace,
801 bool single_click,
802 double single_angle,
803 double single_scale,
804 bool pick_to_size,
805 bool pick_to_presence,
806 bool pick_to_color,
807 bool pick_to_opacity,
808 bool invert_picked,
809 double gamma_picked ,
810 double rand_picked)
811{
812 bool did = false;
813
814 {
815 // convert 3D boxes to ordinary groups before spraying their shapes
816 // TODO: ideally the original object is preserved.
817 if (auto box = cast<SPBox3D>(item)) {
818 set->remove(item);
819 item = box->convert_to_group();
820 set->add(item);
821 }
822 }
823
824 double _fid = single_click ? 0 : g_random_double_range(0, 1);
825 double angle = single_click ? single_angle : g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI );
826 double _scale = single_click ? single_scale : g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 );
827 if(!single_click && usepressurescale){
828 _scale = pressure;
829 }
830 double dr; double dp;
831 random_position( dr, dp, mean, standard_deviation, _distrib );
832 dr=dr*radius;
833
835 if (auto bbox = item->documentVisualBounds()) {
836 if(_fid <= population || no_overlap)
837 {
838 SPDocument *doc = item->document;
839 gchar const * spray_origin;
840 if(!item->getAttribute("inkscape:spray-origin")){
841 spray_origin = g_strdup_printf("#%s", item->getId());
842 } else {
843 spray_origin = item->getAttribute("inkscape:spray-origin");
844 }
845 Geom::Point center = item->getCenter(false);
846 Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-bbox->midpoint());
847 if (single_click) {
848 move = p-bbox->midpoint();
849 }
851 bool stop = false;
852
853 if (mode == SPRAY_MODE_ERASER ||
854 pick_no_overlap || no_overlap || picker ||
855 !over_transparent || !over_no_transparent)
856 {
857 for (auto i : {0,1}) {
858 if (!fit_item(desktop
859 , set
860 , item
861 , bbox
862 , move
863 , center
864 , mode
865 , angle
866 , _scale
867 , scale
868 , picker
869 , pick_center
870 , pick_inverse_value
871 , pick_fill
872 , pick_stroke
873 , pick_no_overlap
874 , over_no_transparent
875 , over_transparent
876 , no_overlap
877 , offset
878 , css
879 , false
880 , pick
881 , do_trace
882 , single_click
883 , pick_to_size
884 , pick_to_presence
885 , pick_to_color
886 , pick_to_opacity
887 , invert_picked
888 , gamma_picked
889 , rand_picked)) {
890 if (no_overlap && i == 0) {
891 move = p-bbox->midpoint() * desktop->doc2dt().withoutTranslation();
892 continue;
893 } else {
894 stop = true;
895 break;
896 }
897 }
898 }
899 if (stop) {
900 return false;
901 }
902 }
903 SPItem *item_copied;
904 // Duplicate
905 Inkscape::XML::Document* xml_doc = doc->getReprDoc();
906 Inkscape::XML::Node *old_repr = item->getRepr();
907 Inkscape::XML::Node *parent = old_repr->parent();
908 Inkscape::XML::Node *clone = nullptr;
909 if (mode == SPRAY_MODE_CLONE) {
910 // Creation of the clone
911 clone = xml_doc->createElement("svg:use");
912 // Ad the clone to the list of the parent's children
913 parent->appendChild(clone);
914 // Generates the link between parent and child attributes
915 if(!clone->attribute("inkscape:spray-origin")){
916 clone->setAttribute("inkscape:spray-origin", spray_origin);
917 }
918 gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id"));
919 clone->setAttribute("xlink:href", href_str);
920 g_free(href_str);
921
922 SPObject *clone_object = doc->getObjectByRepr(clone);
923 item_copied = cast<SPItem>(clone_object);
924 } else {
925 Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
926 if(!copy->attribute("inkscape:spray-origin")){
927 copy->setAttribute("inkscape:spray-origin", spray_origin);
928 }
929 parent->appendChild(copy);
930 SPObject *new_obj = doc->getObjectByRepr(copy);
931 item_copied = cast<SPItem>(new_obj); // Conversion object->item
932 }
933 // Conversion object->item
934
935 if (single_click && item->isCenterSet()) {
936 item_copied->unsetCenter();
937 item_copied->updateRepr();
938 center = bbox->midpoint();
939 }
940
941 auto translate = Geom::Translate(move * desktop->doc2dt().withoutTranslation());
942 auto affine = transform_around_point(center, Geom::Scale(_scale * scale) * Geom::Rotate(angle));
943 transform_keep_center(item_copied, affine * translate, single_click ? center * translate : center);
944
945 if(picker){
946 sp_desktop_apply_css_recursive(item_copied, css, true);
947 }
948 if (mode == SPRAY_MODE_CLONE) {
950 }
951 did = true;
952 }
953 }
954 }
955#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
956 else if (mode == SPRAY_MODE_SINGLE_PATH) {
957 if (item) {
958 SPDocument *doc = item->document;
959 Inkscape::XML::Document* xml_doc = doc->getReprDoc();
960 Inkscape::XML::Node *old_repr = item->getRepr();
961 Inkscape::XML::Node *parent = old_repr->parent();
962
963 if (auto bbox = item->documentVisualBounds()) {
964 if (_fid <= population) { // Rules the population of objects sprayed
965 // Duplicates the parent item
966 Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
967 gchar const * spray_origin;
968 if(!copy->attribute("inkscape:spray-origin")){
969 spray_origin = g_strdup_printf("#%s", old_repr->attribute("id"));
970 } else {
971 spray_origin = copy->attribute("inkscape:spray-origin");
972 }
973 parent->appendChild(copy);
974 SPObject *new_obj = doc->getObjectByRepr(copy);
975 auto item_copied = cast<SPItem>(new_obj);
976
977 // Move around the cursor
978 Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-bbox->midpoint());
979
980 Geom::Point center = item->getCenter(false);
981 auto translate = Geom::Translate(move * desktop->doc2dt().withoutTranslation());
982 auto affine = transform_around_point(center, Geom::Scale(_scale * scale) * Geom::Rotate(angle));
983 transform_keep_center(item_copied, affine * translate, center);
984
985 // Union
986 // only works if no groups in selection
987 auto object_set_tmp = ObjectSet{desktop};
988 object_set_tmp.add(item_copied);
989 object_set_tmp.removeLPESRecursive(true);
990 if (is<SPUse>(object_set_tmp.objects().front())) {
991 object_set_tmp.unlinkRecursive(true);
992 }
993 if (single_path_output) { // Previous result
994 object_set_tmp.add(single_path_output);
995 }
996 object_set_tmp.pathUnion(true);
997 single_path_output = object_set_tmp.items().front();
998 for (auto item : object_set_tmp.items()) {
999 auto repr = item->getRepr();
1000 repr->setAttribute("inkscape:spray-origin", spray_origin);
1001 }
1003 did = true;
1004 }
1005 }
1006 }
1007 }
1008#endif
1009 return did;
1010}
1011
1012static bool sp_spray_dilate(SprayTool *tc, Geom::Point p, Geom::Point vector, bool reverse, bool force = false)
1013{
1014 SPDesktop *desktop = tc->getDesktop();
1016 if (set->isEmpty()) {
1017 return false;
1018 }
1019
1020 bool did = false;
1021 double radius = get_dilate_radius(tc);
1022 double population = get_population(tc);
1023 if (radius == 0 || (population == 0 && !force)) {
1024 return false;
1025 }
1026 double path_mean = get_path_mean(tc);
1027 if (radius == 0 || path_mean == 0) {
1028 return false;
1029 }
1030 double path_standard_deviation = get_path_standard_deviation(tc);
1031 if (radius == 0 || path_standard_deviation == 0) {
1032 return false;
1033 }
1034 double move_mean = get_move_mean(tc);
1035 double move_standard_deviation = get_move_standard_deviation(tc);
1036
1037 {
1038 for(auto item : tc->items){
1039 g_assert(item != nullptr);
1041 }
1042
1043 for(auto item : tc->items){
1044 g_assert(item != nullptr);
1046 , set
1047 , item
1048 , tc->single_path_output
1049 , p, vector
1050 , tc->mode
1051 , radius
1052 , population
1053 , tc->scale
1054 , tc->scale_variation
1055 , reverse
1056 , move_mean
1057 , move_standard_deviation
1058 , tc->ratio
1059 , tc->tilt
1060 , tc->rotation_variation
1061 , tc->distrib
1062 , tc->no_overlap
1063 , tc->picker
1064 , tc->pick_center
1065 , tc->pick_inverse_value
1066 , tc->pick_fill
1067 , tc->pick_stroke
1068 , tc->pick_no_overlap
1070 , tc->over_transparent
1071 , tc->offset
1072 , tc->usepressurescale
1073 , get_pressure(tc)
1074 , tc->pick
1075 , tc->do_trace
1076 , tc->single_click
1077 , tc->single_angle
1078 , tc->single_scale
1079 , tc->pick_to_size
1080 , tc->pick_to_presence
1081 , tc->pick_to_color
1082 , tc->pick_to_opacity
1083 , tc->invert_picked
1084 , tc->gamma_picked
1085 , tc->rand_picked)) {
1086 did = true;
1087 }
1088 }
1089
1090 for(auto item : tc->items){
1091 g_assert(item != nullptr);
1093 }
1094 }
1095
1096 return did;
1097}
1098
1100{
1101 double radius = get_dilate_radius(tc);
1102 Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) *
1103 Geom::Rotate(tc->tilt) *
1104 Geom::Translate(tc->getDesktop()->point()));
1105
1106 Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); // Unit circle centered at origin.
1107 path *= sm;
1108 tc->dilate_area->set_bpath(path);
1109 tc->dilate_area->set_visible(true);
1110 if (tc->single_click && tc->items.size() == 1 && tc->mode != SPRAY_MODE_SINGLE_PATH && tc->mode != SPRAY_MODE_ERASER) {
1111 Geom::PathVector shapes;
1112 get_paths(tc->items[0], shapes);
1113 shapes *= Geom::Translate(tc->getDesktop()->point());
1114 tc->shapes_area->set_bpath(shapes);
1115 tc->shapes_area->set_visible(true);
1116 } else {
1117 tc->shapes_area->set_visible(false);
1118 }
1119}
1120
1121static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift)
1122{
1123 // Select the button mode
1124 auto tb = dynamic_cast<UI::Toolbar::SprayToolbar*>(tc->getDesktop()->get_toolbar_by_name("SprayToolbar"));
1125
1126 if(tb) {
1127 tb->setMode(mode);
1128 } else {
1129 std::cerr << "Could not access Spray toolbar" << std::endl;
1130 }
1131
1132 // Need to set explicitly, because the prefs may not have changed by the previous
1133 tc->mode = mode;
1134 tc->update_cursor(with_shift);
1135}
1136
1138{
1139 bool ret = false;
1140
1141 inspect_event(event,
1142 [&] (EnterEvent const &event) {
1143 dilate_area->set_visible(true);
1144 shapes_area->set_visible(true);
1145 },
1146 [&] (LeaveEvent const &event) {
1147 dilate_area->set_visible(false);
1148 shapes_area->set_visible(false);
1149 },
1150 [&] (ButtonPressEvent const &event) {
1151 if (event.num_press == 1 && event.button == 1) {
1153 xyp = event.pos.floor();
1155 Geom::Point const motion_dt(_desktop->w2d(event.pos));
1156 last_push = _desktop->dt2doc(motion_dt);
1157
1158 sp_spray_extinput(this, event.extinput);
1159
1161 is_dilating = true;
1162 has_dilated = false;
1163 is_drawing = false;
1165 single_path_output = nullptr;
1166 }
1167
1168 ret = true;
1169 within_tolerance = true;
1170 single_click = true;
1171 }
1172 }
1173 if (event.num_press == 1 && event.button == 3) {
1174 //reset preview on right click
1175 items.clear();
1176 ret = true;
1177 }
1178 },
1179 [&] (MotionEvent const &event) {
1180
1181 Geom::Point motion_dt(_desktop->w2d(event.pos));
1182 Geom::Point motion_doc(_desktop->dt2doc(motion_dt));
1183 if (!has_dilated && items.empty() && mode != SPRAY_MODE_SINGLE_PATH) {
1184 update_cursor(true);
1185 if (!object_set.isEmpty()) {
1186 // select a random item from the ones selected to spay to preview and apply on single click
1187 auto randintem = object_set.items_vector()[g_random_int_range(0, object_set.size())];
1188 release_connection = randintem->connectRelease([this] (auto) { items.clear(); });
1189 items.clear();
1190 items.push_back(randintem);
1191 shapes.clear();
1192 get_paths(randintem, shapes);
1193 single_angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI );
1194 single_scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 );
1197 }
1199 if (a) {
1200 Geom::Translate const s(a->midpoint());
1201 shapes *= s.inverse() * Geom::Scale(single_scale) * s;
1202 shapes *= s.inverse() * Geom::Scale(scale) * s;
1204 }
1205 }
1206 }
1207 // To fix https://bugs.launchpad.net/inkscape/+bug/1458200
1208 // we increase the tolerance because no sensible data for panning
1209 if (within_tolerance && Geom::LInfty(event.pos.floor() - xyp) < tolerance * 3) {
1210 // do not drag if we're within tolerance from origin
1211 return;
1212 }
1213 if (!is_drawing && is_dilating) {
1215 }
1216
1217 // Once the user has moved farther than tolerance from
1218 // the original location (indicating they intend to move
1219 // the object, not click), then always process the motion
1220 // notify coordinates as given (no snapping back to origin)
1221 within_tolerance = false;
1222 single_click = false;
1223 sp_spray_extinput(this, event.extinput);
1224
1225 // Draw the dilating cursor
1226 double radius = get_dilate_radius(this);
1227 Geom::Affine const sm (Geom::Scale(radius/(1-ratio), radius/(1+ratio)) *
1229 Geom::Translate(motion_dt));
1230
1231 Geom::PathVector path = Geom::Path(Geom::Circle(0, 0, 1)); // Unit circle centered at origin.
1232 path *= sm;
1233 dilate_area->set_bpath(path);
1234 dilate_area->set_visible(true);
1235 if (!has_dilated && items.size() == 1 && mode != SPRAY_MODE_SINGLE_PATH && mode != SPRAY_MODE_ERASER) {
1236 shapes *= Geom::Translate(getDesktop()->point());
1237 shapes_area->set_bpath(shapes);
1238 shapes *= Geom::Translate(getDesktop()->point()).inverse();
1239 shapes_area->set_visible(true);
1240 } else {
1241 shapes_area->set_visible(false);
1242 }
1243 unsigned num = items.size();
1244 if (num == 0) {
1245 this->message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray."));
1246 }
1247
1248 // Dilating:
1249 if (is_dilating && ( event.modifiers & GDK_BUTTON1_MASK )) {
1250 sp_spray_dilate(this, motion_doc, motion_doc - last_push, event.modifiers & GDK_SHIFT_MASK ? true : false);
1251 //this->last_push = motion_doc;
1252 is_drawing = true;
1253 has_dilated = true;
1254 // It's slow, so prevent clogging up with events
1255 gobble_motion_events(GDK_BUTTON1_MASK);
1256 ret = true;
1257 }
1258 },
1259 [&] (ScrollEvent const &event) {
1260 if (event.modifiers == GDK_BUTTON1_MASK) {
1261 /* Spray with the scroll */
1262 double temp ;
1263 temp = population;
1264 population = 1.0;
1265 _desktop->setToolboxAdjustmentValue("spray-population", population * 100);
1266 Geom::Point const scroll_dt = _desktop->point();;
1267
1268 if (event.delta.y() != 0 && Inkscape::have_viable_layer(_desktop, defaultMessageContext())) {
1269 last_push = _desktop->dt2doc(scroll_dt);
1270 sp_spray_extinput(this, event.extinput);
1271 if(is_dilating) {
1272 sp_spray_dilate(this, _desktop->dt2doc(scroll_dt), Geom::Point(0, 0), false);
1273 }
1274 population = temp;
1275 _desktop->setToolboxAdjustmentValue("spray-population", population * 100);
1276
1277 ret = true;
1278 }
1279 }
1280 },
1281 [&] (ButtonReleaseEvent const &event) {
1282
1283 Geom::Point const motion_dt(_desktop->w2d(event.pos));
1284 Geom::Point motion_doc(_desktop->dt2doc(motion_dt));
1285
1287 is_drawing = false;
1288
1289 if ((single_click || this->is_dilating) && event.button == 1) {
1290 if (single_click) {
1291 sp_spray_dilate(this, _desktop->dt2doc(motion_dt), motion_doc - this->last_push, event.modifiers & GDK_SHIFT_MASK? true : false, true);
1292 } else if (!this->has_dilated) {
1293 // If we did not rub, do a light tap
1294 pressure = 0.03;
1295 sp_spray_dilate(this, _desktop->dt2doc(motion_dt), Geom::Point(0,0), event.modifiers & GDK_SHIFT_MASK);
1296 }
1298 items.clear();
1299 is_dilating = false;
1300 is_drawing = false;
1301 has_dilated = false;
1302 single_click = false;
1303 switch (mode) {
1304 case SPRAY_MODE_COPY:
1305 DocumentUndo::done(_desktop->getDocument(), _("Spray with copies"), INKSCAPE_ICON("tool-spray"));
1306 break;
1307 case SPRAY_MODE_CLONE:
1308 DocumentUndo::done(_desktop->getDocument(), _("Spray with clones"), INKSCAPE_ICON("tool-spray"));
1309 break;
1311 DocumentUndo::done(_desktop->getDocument(), _("Spray in single path"), INKSCAPE_ICON("tool-spray"));
1312 break;
1313 }
1314 }
1315 },
1316 [&] (KeyPressEvent const &event) {
1317 switch (get_latin_keyval (event)) {
1318 case GDK_KEY_j:
1319 case GDK_KEY_J:
1320 if (mod_shift_only(event)) {
1322 ret = true;
1323 }
1324 break;
1325 case GDK_KEY_k:
1326 case GDK_KEY_K:
1327 if (mod_shift_only(event)) {
1329 ret = true;
1330 }
1331 break;
1332#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
1333 case GDK_KEY_l:
1334 case GDK_KEY_L:
1335 if (mod_shift_only(event)) {
1337 ret = true;
1338 }
1339 break;
1340#endif
1341 case GDK_KEY_Up:
1342 case GDK_KEY_KP_Up:
1343 if (!mod_ctrl_only(event)) {
1344 population += 0.01;
1345 if (population > 1.0) {
1346 population = 1.0;
1347 }
1348 _desktop->setToolboxAdjustmentValue("spray-population", population * 100);
1349 ret = true;
1350 }
1351 break;
1352 case GDK_KEY_Down:
1353 case GDK_KEY_KP_Down:
1354 if (!mod_ctrl_only(event)) {
1355 population -= 0.01;
1356 if (population < 0.0) {
1357 population = 0.0;
1358 }
1359 _desktop->setToolboxAdjustmentValue("spray-population", population * 100);
1360 ret = true;
1361 }
1362 break;
1363 case GDK_KEY_Right:
1364 case GDK_KEY_KP_Right:
1365 if (!mod_ctrl_only(event)) {
1366 width += 0.01;
1367 if (width > 1.0) {
1368 width = 1.0;
1369 }
1370 // The same spinbutton is for alt+x
1371 _desktop->setToolboxAdjustmentValue("spray-width", width * 100);
1373 ret = true;
1374 }
1375 break;
1376 case GDK_KEY_Left:
1377 case GDK_KEY_KP_Left:
1378 if (!mod_ctrl_only(event)) {
1379 width -= 0.01;
1380 if (width < 0.01) {
1381 width = 0.01;
1382 }
1383 _desktop->setToolboxAdjustmentValue("spray-width", width * 100);
1385 ret = true;
1386 }
1387 break;
1388 case GDK_KEY_Home:
1389 case GDK_KEY_KP_Home:
1390 width = 0.01;
1391 _desktop->setToolboxAdjustmentValue("spray-width", width * 100);
1393 ret = true;
1394 break;
1395 case GDK_KEY_End:
1396 case GDK_KEY_KP_End:
1397 width = 1.0;
1398 _desktop->setToolboxAdjustmentValue("spray-width", width * 100);
1400 ret = true;
1401 break;
1402 case GDK_KEY_x:
1403 case GDK_KEY_X:
1404 if (mod_alt_only(event)) {
1405 _desktop->setToolboxFocusTo("spray-width");
1406 ret = true;
1407 }
1408 break;
1409 case GDK_KEY_Shift_L:
1410 case GDK_KEY_Shift_R:
1411 update_cursor(true);
1412 break;
1413 case GDK_KEY_Control_L:
1414 case GDK_KEY_Control_R:
1415 break;
1416 case GDK_KEY_Delete:
1417 case GDK_KEY_KP_Delete:
1418 case GDK_KEY_BackSpace:
1419 ret = deleteSelectedDrag(mod_ctrl_only(event));
1420 break;
1421
1422 default:
1423 break;
1424 }
1425 },
1426 [&] (KeyReleaseEvent const &event) {
1428 switch (get_latin_keyval(event)) {
1429 case GDK_KEY_Shift_L:
1430 case GDK_KEY_Shift_R:
1431 update_cursor(false);
1432 break;
1433 case GDK_KEY_Control_L:
1434 case GDK_KEY_Control_R:
1435 sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), mod_shift(event));
1436 message_context->clear();
1437 break;
1438 default:
1439 // Why is this called here?
1440 sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), mod_shift(event));
1441 break;
1442 }
1443 },
1444 [&] (CanvasEvent const &event) {}
1445 );
1446
1447 return ret || ToolBase::root_handler(event);
1448}
1449
1450} // namespace Inkscape::UI::Tools
1451
1452/*
1453 Local Variables:
1454 mode:c++
1455 c-file-style:"stroustrup"
1456 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1457 indent-tabs-mode:nil
1458 fill-column:99
1459 End:
1460*/
1461// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Circle shape.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Affine withoutTranslation() const
Definition affine.h:169
Set of all points at a fixed distance from the center.
Definition circle.h:55
Axis aligned, non-empty, generic rectangle.
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
OptRect boundsFast() const
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
iterator insert(iterator pos, Path const &p)
Definition pathvector.h:179
iterator end()
Definition pathvector.h:152
Sequence of contiguous curves, aka spline.
Definition path.h:353
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
OptRect boundsFast() const
Get the approximate bounding box.
Definition path.cpp:348
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
void start(Point const &p)
Definition path.cpp:426
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
IntRect roundOutwards() const
Return the smallest integer rectangle which contains this one.
Definition rect.h:141
bool hasZeroArea(Coord eps=EPSILON) const
Check whether the rectangle has zero area up to specified tolerance.
Definition rect.h:113
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
Inkscape::Drawing * get_drawing()
bool set(unsigned int index, double value)
Set a specific channel in the color.
Definition color.cpp:350
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Colors::Color averageColor(Geom::IntRect const &area) const
Definition drawing.cpp:374
bool add(SPObject *object, bool nosignal=false)
Add an SPObject to the set of selected objects.
bool isEmpty()
Returns true if no items are selected.
int size()
Returns size of the selection.
SPObjectRange objects()
Returns the list of selected objects.
std::vector< SPItem * > items_vector()
Definition object-set.h:261
Data type representing a typeless value of a preference.
double getDouble(double def=0.0, Glib::ustring const &unit="") const
Interpret the preference as a floating point value.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
bool getBool(bool def=false) const
Interpret the preference as a Boolean value.
int getInt(int def=0) const
Interpret the preference as an integer.
double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="") const
Interpret the preference as a limited floating point value.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
sigc::scoped_connection release_connection
Definition spray-tool.h:111
std::vector< SPItem * > items
Definition spray-tool.h:85
CanvasItemPtr< CanvasItemBpath > shapes_area
Definition spray-tool.h:84
bool root_handler(CanvasEvent const &event) override
SprayTool(SPDesktop *desktop)
CanvasItemPtr< CanvasItemBpath > dilate_area
Definition spray-tool.h:83
Base class for Event processors.
Definition tool-base.h:107
bool within_tolerance
are we still within tolerance of origin
Definition tool-base.h:148
SPDesktop * getDesktop() const
Definition tool-base.h:125
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
void set_high_motion_precision(bool high_precision=true)
Enable (or disable) high precision for motion events.
Geom::IntPoint xyp
where drag started
Definition tool-base.h:145
virtual bool root_handler(CanvasEvent const &event)
void enableGrDrag(bool enable=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
bool deleteSelectedDrag(bool just_one)
Delete a selected GrDrag point.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Inkscape::CanvasItemGroup * getCanvasControls() const
Definition desktop.h:196
SPDocument * getDocument() const
Definition desktop.h:189
void setToolboxFocusTo(char const *label)
Definition desktop.cpp:1128
unsigned dkey
Definition desktop.h:229
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
Geom::Point point() const
Returns the mouse point in desktop coordinates; if mouse is outside the canvas, returns the center of...
Definition desktop.cpp:378
Gtk::Widget * get_toolbar_by_name(Glib::ustring const &name)
Definition desktop.cpp:1139
Inkscape::CanvasItemDrawing * getCanvasDrawing() const
Definition desktop.h:204
Inkscape::Selection * getSelection() const
Definition desktop.h:188
void setToolboxAdjustmentValue(char const *id, double val)
Definition desktop.cpp:1134
Geom::Affine const & doc2dt() const
Definition desktop.cpp:1337
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Typed SVG document implementation.
Definition document.h:103
const Geom::Affine & dt2doc() const
Desktop to document coordinate transformation.
Definition document.h:270
std::vector< SPItem * > getItemsPartiallyInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden=false, bool take_insensitive=false, bool take_groups=true, bool enter_groups=false, bool enter_layers=true) const
Get items whose bounding box overlaps with given area.
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
Definition sp-item.h:109
void set_i2d_affine(Geom::Affine const &transform)
Definition sp-item.cpp:1827
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1018
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:377
Geom::Affine transform
Definition sp-item.h:138
void setHidden(bool hidden)
Definition sp-item.cpp:241
void unsetCenter()
Definition sp-item.cpp:367
bool updateCenterIfSet(Geom::Point const &center)
Definition sp-item.cpp:357
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
bool isCenterSet() const
Definition sp-item.cpp:372
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
constexpr double SP_RGBA32_A_F(uint32_t v)
Definition utils.h:55
RootCluster root
std::shared_ptr< Css const > css
void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines)
Apply style on object and children, recursively.
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
unsigned int guint32
SVG drawing for display.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
auto floor(Geom::Rect const &rect)
Definition geom.h:130
Macro for icon names used in Inkscape.
SPItem * item
double offset
Interface for locally managing a current status message.
Coord LInfty(Point const &p)
std::string rgba_to_hex(uint32_t value, bool alpha)
Output the RGBA value as a #RRGGBB hex color, if alpha is true then the output will be #RRGGBBAA inst...
Definition utils.cpp:70
static R & release(R &r)
Decrements the reference count of a anchored object.
static void do_trace(BitmapCoordsInfo const &bci, unsigned char *trace_px, SPDesktop *desktop, Geom::Affine const &transform, unsigned min_x, unsigned max_x, unsigned min_y, unsigned max_y, bool union_with_selection)
Perform the bitmap-to-vector tracing and place the traced path onto the document.
static void sp_spray_extinput(SprayTool *tc, ExtendedInput const &ext)
static void transform_keep_center(SPItem *item, Geom::Affine const &affine, Geom::Point const &center)
static double get_width(SprayTool *tc)
static void random_position(double &radius, double &angle, double &a, double &s, int)
Method to handle the distribution of the items.
static double get_move_mean(SprayTool *tc)
static guint32 getPickerData(Geom::IntRect area, SPDesktop *desktop)
static void showHidden(std::vector< SPItem * > items_down)
static void sp_spray_update_area(SprayTool *tc)
static double get_path_mean(SprayTool *tc)
static bool sp_spray_dilate(SprayTool *tc, Geom::Point p, Geom::Point vector, bool reverse, bool force=false)
static double get_population(SprayTool *tc)
static double get_pressure(SprayTool *tc)
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
static double get_move_standard_deviation(SprayTool *tc)
static bool fit_item(SPDesktop *desktop, Inkscape::ObjectSet *set, SPItem *item, Geom::OptRect bbox, Geom::Point &move, Geom::Point center, gint mode, double angle, double &_scale, double scale, bool picker, bool pick_center, bool pick_inverse_value, bool pick_fill, bool pick_stroke, bool pick_no_overlap, bool over_no_transparent, bool over_transparent, bool no_overlap, double offset, SPCSSAttr *css, bool trace_scale, int pick, bool do_trace, bool single_click, bool pick_to_size, bool pick_to_presence, bool pick_to_color, bool pick_to_opacity, bool invert_picked, double gamma_picked, double rand_picked)
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
static void get_paths(SPItem *item, Geom::PathVector &res, bool root=true)
double randomize01(double val, double rand)
Randomizes val by rand, with 0 < val < 1 and all values (including 0, 1) having the same probability ...
static double get_path_standard_deviation(SprayTool *tc)
static double get_dilate_radius(SprayTool *tc)
static void sp_spray_transform_path(SPItem *item, Geom::Path &path, Geom::Affine affine, Geom::Point center)
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
static Geom::Affine transform_around_point(Geom::Point center, Geom::Affine const &affine)
Transform the affine around the point.
static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift)
static bool sp_spray_recursive(SPDesktop *desktop, Inkscape::ObjectSet *set, SPItem *item, SPItem *&single_path_output, Geom::Point p, Geom::Point, gint mode, double radius, double population, double &scale, double scale_variation, bool, double mean, double standard_deviation, double ratio, double tilt, double rotation_variation, gint _distrib, bool no_overlap, bool picker, bool pick_center, bool pick_inverse_value, bool pick_fill, bool pick_stroke, bool pick_no_overlap, bool over_no_transparent, bool over_transparent, double offset, bool usepressurescale, double pressure, int pick, bool do_trace, bool single_click, double single_angle, double single_scale, bool pick_to_size, bool pick_to_presence, bool pick_to_color, bool pick_to_opacity, bool invert_picked, double gamma_picked, double rand_picked)
double NormalDistribution(double mu, double sigma)
This function returns pseudo-random numbers from a normal distribution.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
bool mod_ctrl_only(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_shift(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
@ ERROR_MESSAGE
Definition message.h:29
@ NORMAL_MESSAGE
Definition message.h:26
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
int mode
Piecewise< SBasis > log(Interval in)
Definition pw-funcs.cpp:37
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value)
Set a style property to a standard string.
Definition repr-css.cpp:235
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
int num
Definition scribble.cpp:47
Geom::Affine i2anc_affine(SPObject const *object, SPObject const *ancestor)
Definition sp-item.cpp:1787
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
The pointer has entered a widget or item.
Extended input data associated to events generated by graphics tablets.
std::optional< double > pressure
A key has been pressed.
A key has been released.
The pointer has exited a widget or item.
Movement of the mouse pointer.
Scroll the item or widget by the provided amount.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
SPDesktop * desktop
double height
double width