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 <numeric>
25 #include <vector>
26 #include <tuple>
27 
28 #include <gdk/gdkkeysyms.h>
29 #include <glibmm/i18n.h>
30 
31 #include <2geom/circle.h>
32 
33 
34 #include "context-fns.h"
35 #include "desktop-events.h"
36 #include "desktop-style.h"
37 #include "desktop.h"
38 #include "document-undo.h"
39 #include "document.h"
40 #include "filter-chemistry.h"
41 #include "inkscape.h"
42 #include "include/macros.h"
43 #include "message-context.h"
44 #include "path-chemistry.h"
45 #include "selection.h"
46 #include "verbs.h"
47 
48 #include "display/cairo-utils.h"
49 #include "display/curve.h"
50 #include "display/drawing-context.h"
51 #include "display/drawing.h"
52 #include "display/control/canvas-item-bpath.h"
53 #include "display/control/canvas-item-drawing.h"
54 
55 #include "helper/action.h"
56 
57 #include "object/box3d.h"
58 #include "object/sp-item-transform.h"
59 
60 #include "svg/svg.h"
61 #include "svg/svg-color.h"
62 
63 #include "ui/toolbar/spray-toolbar.h"
64 #include "ui/tools/spray-tool.h"
65 
66 
67 using Inkscape::DocumentUndo;
68 
69 #define DDC_RED_RGBA 0xff0000ff
70 #define DYNA_MIN_WIDTH 1.0e-6
71 
72 // Disabled in 0.91 because of Bug #1274831 (crash, spraying an object
73 // with the mode: spray object in single path)
74 // Please enable again when working on 1.0
75 #define ENABLE_SPRAY_MODE_SINGLE_PATH
76 
77 namespace Inkscape {
78 namespace UI {
79 namespace Tools {
80 
81 enum {
82     PICK_COLOR,
83     PICK_OPACITY,
84     PICK_R,
85     PICK_G,
86     PICK_B,
87     PICK_H,
88     PICK_S,
89     PICK_L
90 };
91 
getPrefsPath()92 const std::string& SprayTool::getPrefsPath() {
93     return SprayTool::prefsPath;
94 }
95 
96 const std::string SprayTool::prefsPath = "/tools/spray";
97 
98 /**
99  * This function returns pseudo-random numbers from a normal distribution
100  * @param mu : mean
101  * @param sigma : standard deviation ( > 0 )
102  */
NormalDistribution(double mu,double sigma)103 inline double NormalDistribution(double mu, double sigma)
104 {
105   // use Box Muller's algorithm
106   return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) );
107 }
108 
109 /* Method to rotate items */
sp_spray_rotate_rel(Geom::Point c,SPDesktop *,SPItem * item,Geom::Rotate const & rotation)110 static void sp_spray_rotate_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Rotate const &rotation)
111 {
112     Geom::Translate const s(c);
113     Geom::Affine affine = s.inverse() * rotation * s;
114     // Rotate item.
115     item->set_i2d_affine(item->i2dt_affine() * affine);
116     // Use each item's own transform writer, consistent with sp_selection_apply_affine()
117     item->doWriteTransform(item->transform);
118     // Restore the center position (it's changed because the bbox center changed)
119     if (item->isCenterSet()) {
120         item->setCenter(c);
121         item->updateRepr();
122     }
123 }
124 
125 /* Method to scale items */
sp_spray_scale_rel(Geom::Point c,SPDesktop *,SPItem * item,Geom::Scale const & scale)126 static void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale)
127 {
128     Geom::Translate const s(c);
129     item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s);
130     item->doWriteTransform(item->transform);
131 }
132 
SprayTool()133 SprayTool::SprayTool()
134     : ToolBase("spray.svg", false)
135     , pressure(TC_DEFAULT_PRESSURE)
136     , dragging(false)
137     , usepressurewidth(false)
138     , usepressurepopulation(false)
139     , usepressurescale(false)
140     , usetilt(false)
141     , usetext(false)
142     , width(0.2)
143     , ratio(0)
144     , tilt(0)
145     , rotation_variation(0)
146     , population(0)
147     , scale_variation(1)
148     , scale(1)
149     , mean(0.2)
150     , standard_deviation(0.2)
151     , distrib(1)
152     , mode(0)
153     , is_drawing(false)
154     , is_dilating(false)
155     , has_dilated(false)
156     , dilate_area(nullptr)
157     , no_overlap(false)
158     , picker(false)
159     , pick_center(true)
160     , pick_inverse_value(false)
161     , pick_fill(false)
162     , pick_stroke(false)
163     , pick_no_overlap(false)
164     , over_transparent(true)
165     , over_no_transparent(true)
166     , offset(0)
167     , pick(0)
168     , do_trace(false)
169     , pick_to_size(false)
170     , pick_to_presence(false)
171     , pick_to_color(false)
172     , pick_to_opacity(false)
173     , invert_picked(false)
174     , gamma_picked(0)
175     , rand_picked(0)
176 {
177 }
178 
~SprayTool()179 SprayTool::~SprayTool() {
180     if (!object_set.isEmpty()) {
181         object_set.clear();
182     }
183     desktop->getSelection()->restoreBackup();
184     this->enableGrDrag(false);
185     this->style_set_connection.disconnect();
186 
187     if (this->dilate_area) {
188         delete this->dilate_area;
189         this->dilate_area = nullptr;
190     }
191 }
192 
update_cursor(bool)193 void SprayTool::update_cursor(bool /*with_shift*/) {
194     guint num = 0;
195     gchar *sel_message = nullptr;
196 
197     if (!desktop->selection->isEmpty()) {
198         num = (guint) boost::distance(desktop->selection->items());
199         sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
200     } else {
201         sel_message = g_strdup_printf("%s", _("<b>Nothing</b> selected"));
202     }
203 
204     switch (this->mode) {
205         case SPRAY_MODE_COPY:
206             this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>copies</b> of the initial selection."), sel_message);
207             break;
208         case SPRAY_MODE_CLONE:
209             this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>clones</b> of the initial selection."), sel_message);
210             break;
211         case SPRAY_MODE_SINGLE_PATH:
212             this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray in a <b>single path</b> of the initial selection."), sel_message);
213             break;
214         default:
215             break;
216     }
217 
218     this->sp_event_context_update_cursor();
219     g_free(sel_message);
220 }
221 
setup()222 void SprayTool::setup() {
223     ToolBase::setup();
224 
225     {
226         dilate_area = new Inkscape::CanvasItemBpath(desktop->getCanvasControls());
227         dilate_area->set_stroke(0xff9900ff);
228         dilate_area->set_fill(0x0, SP_WIND_RULE_EVENODD);
229         dilate_area->hide();
230     }
231 
232     this->is_drawing = false;
233 
234     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
235     prefs->setBool("/dialogs/clonetiler/dotrace", false);
236     if (prefs->getBool("/tools/spray/selcue")) {
237         this->enableSelectionCue();
238     }
239     if (prefs->getBool("/tools/spray/gradientdrag")) {
240         this->enableGrDrag();
241     }
242     desktop->getSelection()->setBackup();
243     sp_event_context_read(this, "distrib");
244     sp_event_context_read(this, "width");
245     sp_event_context_read(this, "ratio");
246     sp_event_context_read(this, "tilt");
247     sp_event_context_read(this, "rotation_variation");
248     sp_event_context_read(this, "scale_variation");
249     sp_event_context_read(this, "mode");
250     sp_event_context_read(this, "population");
251     sp_event_context_read(this, "mean");
252     sp_event_context_read(this, "standard_deviation");
253     sp_event_context_read(this, "usepressurewidth");
254     sp_event_context_read(this, "usepressurepopulation");
255     sp_event_context_read(this, "usepressurescale");
256     sp_event_context_read(this, "Scale");
257     sp_event_context_read(this, "offset");
258     sp_event_context_read(this, "picker");
259     sp_event_context_read(this, "pick_center");
260     sp_event_context_read(this, "pick_inverse_value");
261     sp_event_context_read(this, "pick_fill");
262     sp_event_context_read(this, "pick_stroke");
263     sp_event_context_read(this, "pick_no_overlap");
264     sp_event_context_read(this, "over_no_transparent");
265     sp_event_context_read(this, "over_transparent");
266     sp_event_context_read(this, "no_overlap");
267 }
268 
setCloneTilerPrefs()269 void SprayTool::setCloneTilerPrefs() {
270     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
271     this->do_trace = prefs->getBool("/dialogs/clonetiler/dotrace", false);
272     this->pick = prefs->getInt("/dialogs/clonetiler/pick");
273     this->pick_to_size = prefs->getBool("/dialogs/clonetiler/pick_to_size", false);
274     this->pick_to_presence = prefs->getBool("/dialogs/clonetiler/pick_to_presence", false);
275     this->pick_to_color = prefs->getBool("/dialogs/clonetiler/pick_to_color", false);
276     this->pick_to_opacity = prefs->getBool("/dialogs/clonetiler/pick_to_opacity", false);
277     this->rand_picked = 0.01 * prefs->getDoubleLimited("/dialogs/clonetiler/rand_picked", 0, 0, 100);
278     this->invert_picked = prefs->getBool("/dialogs/clonetiler/invert_picked", false);
279     this->gamma_picked = prefs->getDoubleLimited("/dialogs/clonetiler/gamma_picked", 0, -10, 10);
280 }
281 
set(const Inkscape::Preferences::Entry & val)282 void SprayTool::set(const Inkscape::Preferences::Entry& val) {
283     Glib::ustring path = val.getEntryName();
284 
285     if (path == "mode") {
286         this->mode = val.getInt();
287         this->update_cursor(false);
288     } else if (path == "width") {
289         this->width = 0.01 * CLAMP(val.getInt(10), 1, 100);
290     } else if (path == "usepressurewidth") {
291         this->usepressurewidth = val.getBool();
292     } else if (path == "usepressurepopulation") {
293         this->usepressurepopulation = val.getBool();
294     } else if (path == "usepressurescale") {
295         this->usepressurescale = val.getBool();
296     } else if (path == "population") {
297         this->population = 0.01 * CLAMP(val.getInt(10), 1, 100);
298     } else if (path == "rotation_variation") {
299         this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0);
300     } else if (path == "scale_variation") {
301         this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0);
302     } else if (path == "standard_deviation") {
303         this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100);
304     } else if (path == "mean") {
305         this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100);
306 // Not implemented in the toolbar and preferences yet
307     } else if (path == "distribution") {
308         this->distrib = val.getInt(1);
309     } else if (path == "tilt") {
310         this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0);
311     } else if (path == "ratio") {
312         this->ratio = CLAMP(val.getDouble(), 0.0, 0.9);
313     } else if (path == "offset") {
314         this->offset = val.getDoubleLimited(100.0, 0, 1000.0);
315     } else if (path == "pick_center") {
316         this->pick_center =  val.getBool(true);
317     } else if (path == "pick_inverse_value") {
318         this->pick_inverse_value =  val.getBool(false);
319     } else if (path == "pick_fill") {
320         this->pick_fill =  val.getBool(false);
321     } else if (path == "pick_stroke") {
322         this->pick_stroke =  val.getBool(false);
323     } else if (path == "pick_no_overlap") {
324         this->pick_no_overlap =  val.getBool(false);
325     } else if (path == "over_no_transparent") {
326         this->over_no_transparent =  val.getBool(true);
327     } else if (path == "over_transparent") {
328         this->over_transparent =  val.getBool(true);
329     } else if (path == "no_overlap") {
330         this->no_overlap = val.getBool(false);
331     } else if (path == "picker") {
332         this->picker =  val.getBool(false);
333     }
334 }
335 
sp_spray_extinput(SprayTool * tc,GdkEvent * event)336 static void sp_spray_extinput(SprayTool *tc, GdkEvent *event)
337 {
338     if (gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &tc->pressure)) {
339         tc->pressure = CLAMP(tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
340     } else {
341         tc->pressure = TC_DEFAULT_PRESSURE;
342     }
343 }
344 
get_width(SprayTool * tc)345 static double get_width(SprayTool *tc)
346 {
347     double pressure = (tc->usepressurewidth? tc->pressure / TC_DEFAULT_PRESSURE : 1);
348     return pressure * tc->width;
349 }
350 
get_dilate_radius(SprayTool * tc)351 static double get_dilate_radius(SprayTool *tc)
352 {
353     return 250 * get_width(tc)/tc->getDesktop()->current_zoom();
354 }
355 
get_path_mean(SprayTool * tc)356 static double get_path_mean(SprayTool *tc)
357 {
358     return tc->mean;
359 }
360 
get_path_standard_deviation(SprayTool * tc)361 static double get_path_standard_deviation(SprayTool *tc)
362 {
363     return tc->standard_deviation;
364 }
365 
get_population(SprayTool * tc)366 static double get_population(SprayTool *tc)
367 {
368     double pressure = (tc->usepressurepopulation? tc->pressure / TC_DEFAULT_PRESSURE : 1);
369     return pressure * tc->population;
370 }
371 
get_pressure(SprayTool * tc)372 static double get_pressure(SprayTool *tc)
373 {
374     double pressure = tc->pressure / TC_DEFAULT_PRESSURE;
375     return pressure;
376 }
377 
get_move_mean(SprayTool * tc)378 static double get_move_mean(SprayTool *tc)
379 {
380     return tc->mean;
381 }
382 
get_move_standard_deviation(SprayTool * tc)383 static double get_move_standard_deviation(SprayTool *tc)
384 {
385     return tc->standard_deviation;
386 }
387 
388 /**
389  * Method to handle the distribution of the items
390  * @param[out]  radius : radius of the position of the sprayed object
391  * @param[out]  angle : angle of the position of the sprayed object
392  * @param[in]   a : mean
393  * @param[in]   s : standard deviation
394  * @param[in]   choice :
395 
396  */
random_position(double & radius,double & angle,double & a,double & s,int)397 static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/)
398 {
399     // angle is taken from an uniform distribution
400     angle = g_random_double_range(0, M_PI*2.0);
401 
402     // radius is taken from a Normal Distribution
403     double radius_temp =-1;
404     while(!((radius_temp >= 0) && (radius_temp <=1 )))
405     {
406         radius_temp = NormalDistribution(a, s);
407     }
408     // Because we are in polar coordinates, a special treatment has to be done to the radius.
409     // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to
410     // be uniformily distributed on the disk (more at the center and less at the boundary).
411     // We counter this effect with a 0.5 exponent. This is empiric.
412     radius = pow(radius_temp, 0.5);
413 
414 }
415 
sp_spray_transform_path(SPItem * item,Geom::Path & path,Geom::Affine affine,Geom::Point center)416 static void sp_spray_transform_path(SPItem * item, Geom::Path &path, Geom::Affine affine, Geom::Point center){
417     path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr).inverse();
418     path *= item->transform.inverse();
419     Geom::Affine dt2p;
420     if (item->parent) {
421         dt2p = static_cast<SPItem *>(item->parent)->i2dt_affine().inverse();
422     } else {
423         dt2p = item->document->dt2doc();
424     }
425     Geom::Affine i2dt = item->i2dt_affine() * Geom::Translate(center).inverse() * affine * Geom::Translate(center);
426     path *= i2dt * dt2p;
427     path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr);
428 }
429 
430 /**
431 Randomizes \a val by \a rand, with 0 < val < 1 and all values (including 0, 1) having the same
432 probability of being displaced.
433  */
randomize01(double val,double rand)434 double randomize01(double val, double rand)
435 {
436     double base = MIN (val - rand, 1 - 2*rand);
437     if (base < 0) {
438         base = 0;
439     }
440     val = base + g_random_double_range (0, MIN (2 * rand, 1 - base));
441     return CLAMP(val, 0, 1); // this should be unnecessary with the above provisions, but just in case...
442 }
443 
getPickerData(Geom::IntRect area,SPDesktop * desktop)444 static guint32 getPickerData(Geom::IntRect area, SPDesktop *desktop)
445 {
446     Inkscape::CanvasItemDrawing *canvas_item_drawing = desktop->getCanvasDrawing();
447     Inkscape::Drawing *drawing = canvas_item_drawing->get_drawing();
448 
449     // Ensure drawing up-to-date. (Is this really necessary?)
450     drawing->update();
451 
452     // Get average color.
453     double R, G, B, A;
454     drawing->average_color(area, R, G, B, A);
455 
456     //this can fix the bug #1511998 if confirmed
457     if ( A < 1e-6) {
458         R = 1.0;
459         G = 1.0;
460         B = 1.0;
461     }
462 
463     return SP_RGBA32_F_COMPOSE(R, G, B, A);
464 }
465 
showHidden(std::vector<SPItem * > items_down)466 static void showHidden(std::vector<SPItem *> items_down){
467     for (auto item_hidden : items_down) {
468         item_hidden->setHidden(false);
469         item_hidden->updateRepr();
470     }
471 }
472 //todo: maybe move same parameter to preferences
fit_item(SPDesktop * desktop,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 pick_to_size,bool pick_to_presence,bool pick_to_color,bool pick_to_opacity,bool invert_picked,double gamma_picked,double rand_picked)473 static bool fit_item(SPDesktop *desktop,
474                      SPItem *item,
475                      Geom::OptRect bbox,
476                      Geom::Point &move,
477                      Geom::Point center,
478                      gint mode,
479                      double angle,
480                      double &_scale,
481                      double scale,
482                      bool picker,
483                      bool pick_center,
484                      bool pick_inverse_value,
485                      bool pick_fill,
486                      bool pick_stroke,
487                      bool pick_no_overlap,
488                      bool over_no_transparent,
489                      bool over_transparent,
490                      bool no_overlap,
491                      double offset,
492                      SPCSSAttr *css,
493                      bool trace_scale,
494                      int pick,
495                      bool do_trace,
496                      bool pick_to_size,
497                      bool pick_to_presence,
498                      bool pick_to_color,
499                      bool pick_to_opacity,
500                      bool invert_picked,
501                      double gamma_picked ,
502                      double rand_picked)
503 {
504     SPDocument *doc = item->document;
505     double width = bbox->width();
506     double height = bbox->height();
507     double offset_width = (offset * width)/100.0 - (width);
508     if(offset_width < 0 ){
509         offset_width = 0;
510     }
511     double offset_height = (offset * height)/100.0 - (height);
512     if(offset_height < 0 ){
513         offset_height = 0;
514     }
515     if(picker && pick_to_size && !trace_scale && do_trace){
516         _scale = 0.1;
517     }
518     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));
519     Geom::Path path;
520     path.start(Geom::Point(bbox_procesed->left(), bbox_procesed->top()));
521     path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->top()));
522     path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->bottom()));
523     path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->left(), bbox_procesed->bottom()));
524     path.close(true);
525     sp_spray_transform_path(item, path, Geom::Scale(_scale), center);
526     sp_spray_transform_path(item, path, Geom::Scale(scale), center);
527     sp_spray_transform_path(item, path, Geom::Rotate(angle), center);
528     path *= Geom::Translate(move);
529     bbox_procesed = path.boundsFast();
530     double bbox_left_main = bbox_procesed->left();
531     double bbox_right_main = bbox_procesed->right();
532     double bbox_top_main = bbox_procesed->top();
533     double bbox_bottom_main = bbox_procesed->bottom();
534     double width_transformed = bbox_procesed->width();
535     double height_transformed = bbox_procesed->height();
536     Geom::Point mid_point = desktop->d2w(bbox_procesed->midpoint() * desktop->doc2dt());
537     Geom::IntRect area = Geom::IntRect::from_xywh(floor(mid_point[Geom::X]), floor(mid_point[Geom::Y]), 1, 1);
538     guint32 rgba = getPickerData(area, desktop);
539     guint32 rgba2 = 0xffffff00;
540     Geom::Rect rect_sprayed(desktop->d2w(Geom::Point(bbox_left_main,bbox_top_main)), desktop->d2w(Geom::Point(bbox_right_main,bbox_bottom_main)));
541     if (!rect_sprayed.hasZeroArea()) {
542         rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
543     }
544     if(pick_no_overlap) {
545         if(rgba != rgba2) {
546             if(mode != SPRAY_MODE_ERASER) {
547                 return false;
548             }
549         }
550     }
551     if(!pick_center) {
552         rgba = rgba2;
553     }
554     if(!over_transparent && (SP_RGBA32_A_F(rgba) == 0 || SP_RGBA32_A_F(rgba) < 1e-6)) {
555         if(mode != SPRAY_MODE_ERASER) {
556             return false;
557         }
558     }
559     if(!over_no_transparent && SP_RGBA32_A_F(rgba) > 0) {
560         if(mode != SPRAY_MODE_ERASER) {
561             return false;
562         }
563     }
564     if(offset < 100 ) {
565         offset_width = ((99.0 - offset) * width_transformed)/100.0 - width_transformed;
566         offset_height = ((99.0 - offset) * height_transformed)/100.0 - height_transformed;
567     } else {
568         offset_width = 0;
569         offset_height = 0;
570     }
571     std::vector<SPItem*> items_down = desktop->getDocument()->getItemsPartiallyInBox(desktop->dkey, *bbox_procesed);
572     Inkscape::Selection *selection = desktop->getSelection();
573     if (selection->isEmpty()) {
574         return false;
575     }
576     std::vector<SPItem*> const items_selected(selection->items().begin(), selection->items().end());
577     std::vector<SPItem*> items_down_erased;
578     for (std::vector<SPItem*>::const_iterator i=items_down.begin(); i!=items_down.end(); ++i) {
579         SPItem *item_down = *i;
580         Geom::OptRect bbox_down = item_down->documentVisualBounds();
581         double bbox_left = bbox_down->left();
582         double bbox_top = bbox_down->top();
583         gchar const * item_down_sharp = g_strdup_printf("#%s", item_down->getId());
584         items_down_erased.push_back(item_down);
585         for (auto item_selected : items_selected) {
586             gchar const * spray_origin;
587             if(!item_selected->getAttribute("inkscape:spray-origin")){
588                 spray_origin = g_strdup_printf("#%s", item_selected->getId());
589             } else {
590                 spray_origin = item_selected->getAttribute("inkscape:spray-origin");
591             }
592             if(strcmp(item_down_sharp, spray_origin) == 0 ||
593                 (item_down->getAttribute("inkscape:spray-origin") &&
594                 strcmp(item_down->getAttribute("inkscape:spray-origin"),spray_origin) == 0 ))
595             {
596                 if(mode == SPRAY_MODE_ERASER) {
597                     if(strcmp(item_down_sharp, spray_origin) != 0 && !selection->includes(item_down) ){
598                         item_down->deleteObject();
599                         items_down_erased.pop_back();
600                         break;
601                     }
602                 } else if(no_overlap) {
603                     if(!(offset_width < 0 && offset_height < 0 && std::abs(bbox_left - bbox_left_main) > std::abs(offset_width) &&
604                 std::abs(bbox_top - bbox_top_main) > std::abs(offset_height))){
605                         if(!no_overlap && (picker || over_transparent || over_no_transparent)){
606                             showHidden(items_down);
607                         }
608                         return false;
609                     }
610                 } else if(picker || over_transparent || over_no_transparent) {
611                     item_down->setHidden(true);
612                     item_down->updateRepr();
613                 }
614             }
615         }
616     }
617     if(mode == SPRAY_MODE_ERASER){
618         if(!no_overlap && (picker || over_transparent || over_no_transparent)){
619             showHidden(items_down_erased);
620         }
621         return false;
622     }
623     if(picker || over_transparent || over_no_transparent){
624         if(!no_overlap){
625             doc->ensureUpToDate();
626             rgba = getPickerData(area, desktop);
627             if (!rect_sprayed.hasZeroArea()) {
628                 rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
629             }
630         }
631         if(pick_no_overlap){
632             if(rgba != rgba2){
633                 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
634                     showHidden(items_down);
635                 }
636                 return false;
637             }
638         }
639         if(!pick_center){
640             rgba = rgba2;
641         }
642         double opacity = 1.0;
643         gchar color_string[32]; *color_string = 0;
644         float r = SP_RGBA32_R_F(rgba);
645         float g = SP_RGBA32_G_F(rgba);
646         float b = SP_RGBA32_B_F(rgba);
647         float a = SP_RGBA32_A_F(rgba);
648         if(!over_transparent && (a == 0 || a < 1e-6)){
649             if(!no_overlap && (picker || over_transparent || over_no_transparent)){
650                 showHidden(items_down);
651             }
652             return false;
653         }
654         if(!over_no_transparent && a > 0){
655             if(!no_overlap && (picker || over_transparent || over_no_transparent)){
656                 showHidden(items_down);
657             }
658             return false;
659         }
660 
661         if(picker && do_trace){
662             float hsl[3];
663             SPColor::rgb_to_hsl_floatv (hsl, r, g, b);
664 
665             gdouble val = 0;
666             switch (pick) {
667             case PICK_COLOR:
668                 val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max
669                 break;
670             case PICK_OPACITY:
671                 val = a;
672                 break;
673             case PICK_R:
674                 val = r;
675                 break;
676             case PICK_G:
677                 val = g;
678                 break;
679             case PICK_B:
680                 val = b;
681                 break;
682             case PICK_H:
683                 val = hsl[0];
684                 break;
685             case PICK_S:
686                 val = hsl[1];
687                 break;
688             case PICK_L:
689                 val = 1 - hsl[2];
690                 break;
691             default:
692                 break;
693             }
694 
695             if (rand_picked > 0) {
696                 val = randomize01 (val, rand_picked);
697                 r = randomize01 (r, rand_picked);
698                 g = randomize01 (g, rand_picked);
699                 b = randomize01 (b, rand_picked);
700             }
701 
702             if (gamma_picked != 0) {
703                 double power;
704                 if (gamma_picked > 0)
705                     power = 1/(1 + fabs(gamma_picked));
706                 else
707                     power = 1 + fabs(gamma_picked);
708 
709                 val = pow (val, power);
710                 r = pow ((double)r, (double)power);
711                 g = pow ((double)g, (double)power);
712                 b = pow ((double)b, (double)power);
713             }
714 
715             if (invert_picked) {
716                 val = 1 - val;
717                 r = 1 - r;
718                 g = 1 - g;
719                 b = 1 - b;
720             }
721 
722             val = CLAMP (val, 0, 1);
723             r = CLAMP (r, 0, 1);
724             g = CLAMP (g, 0, 1);
725             b = CLAMP (b, 0, 1);
726 
727             // recompose tweaked color
728             rgba = SP_RGBA32_F_COMPOSE(r, g, b, a);
729             if (pick_to_size) {
730                 if(!trace_scale){
731                     if(pick_inverse_value) {
732                         _scale = 1.0 - val;
733                     } else {
734                         _scale = val;
735                     }
736                     if(_scale == 0.0) {
737                         if(!no_overlap && (picker || over_transparent || over_no_transparent)){
738                             showHidden(items_down);
739                         }
740                         return false;
741                     }
742                     if(!fit_item(desktop
743                                  , item
744                                  , bbox
745                                  , move
746                                  , center
747                                  , mode
748                                  , angle
749                                  , _scale
750                                  , scale
751                                  , picker
752                                  , pick_center
753                                  , pick_inverse_value
754                                  , pick_fill
755                                  , pick_stroke
756                                  , pick_no_overlap
757                                  , over_no_transparent
758                                  , over_transparent
759                                  , no_overlap
760                                  , offset
761                                  , css
762                                  , true
763                                  , pick
764                                  , do_trace
765                                  , pick_to_size
766                                  , pick_to_presence
767                                  , pick_to_color
768                                  , pick_to_opacity
769                                  , invert_picked
770                                  , gamma_picked
771                                  , rand_picked)
772                         )
773                     {
774                         if(!no_overlap && (picker || over_transparent || over_no_transparent)){
775                             showHidden(items_down);
776                         }
777                         return false;
778                     }
779                 }
780             }
781 
782             if (pick_to_opacity) {
783                 if(pick_inverse_value) {
784                     opacity *= 1.0 - val;
785                 } else {
786                     opacity *= val;
787                 }
788                 std::stringstream opacity_str;
789                 opacity_str.imbue(std::locale::classic());
790                 opacity_str << opacity;
791                 sp_repr_css_set_property(css, "opacity", opacity_str.str().c_str());
792             }
793             if (pick_to_presence) {
794                 if (g_random_double_range (0, 1) > val) {
795                     //Hiding the element is a way to retain original
796                     //behaviour of tiled clones for presence option.
797                     sp_repr_css_set_property(css, "opacity", "0");
798                 }
799             }
800             if (pick_to_color) {
801                 sp_svg_write_color(color_string, sizeof(color_string), rgba);
802                 if(pick_fill){
803                     sp_repr_css_set_property(css, "fill", color_string);
804                 }
805                 if(pick_stroke){
806                     sp_repr_css_set_property(css, "stroke", color_string);
807                 }
808             }
809             if (opacity < 1e-6) { // invisibly transparent, skip
810                 if(!no_overlap && (picker || over_transparent || over_no_transparent)){
811                     showHidden(items_down);
812                 }
813                 return false;
814             }
815         }
816         if(!do_trace){
817             if(!pick_center){
818                 rgba = rgba2;
819             }
820             if (pick_inverse_value) {
821                 r = 1 - SP_RGBA32_R_F(rgba);
822                 g = 1 - SP_RGBA32_G_F(rgba);
823                 b = 1 - SP_RGBA32_B_F(rgba);
824             } else {
825                 r = SP_RGBA32_R_F(rgba);
826                 g = SP_RGBA32_G_F(rgba);
827                 b = SP_RGBA32_B_F(rgba);
828             }
829             rgba = SP_RGBA32_F_COMPOSE(r, g, b, a);
830             sp_svg_write_color(color_string, sizeof(color_string), rgba);
831             if(pick_fill){
832                 sp_repr_css_set_property(css, "fill", color_string);
833             }
834             if(pick_stroke){
835                 sp_repr_css_set_property(css, "stroke", color_string);
836             }
837         }
838         if(!no_overlap && (picker || over_transparent || over_no_transparent)){
839             showHidden(items_down);
840         }
841     }
842     return true;
843 }
844 
sp_spray_recursive(SPDesktop * desktop,Inkscape::ObjectSet * set,SPItem * item,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 pick_to_size,bool pick_to_presence,bool pick_to_color,bool pick_to_opacity,bool invert_picked,double gamma_picked,double rand_picked)845 static bool sp_spray_recursive(SPDesktop *desktop,
846                                Inkscape::ObjectSet *set,
847                                SPItem *item,
848                                Geom::Point p,
849                                Geom::Point /*vector*/,
850                                gint mode,
851                                double radius,
852                                double population,
853                                double &scale,
854                                double scale_variation,
855                                bool /*reverse*/,
856                                double mean,
857                                double standard_deviation,
858                                double ratio,
859                                double tilt,
860                                double rotation_variation,
861                                gint _distrib,
862                                bool no_overlap,
863                                bool picker,
864                                bool pick_center,
865                                bool pick_inverse_value,
866                                bool pick_fill,
867                                bool pick_stroke,
868                                bool pick_no_overlap,
869                                bool over_no_transparent,
870                                bool over_transparent,
871                                double offset,
872                                bool usepressurescale,
873                                double pressure,
874                                int pick,
875                                bool do_trace,
876                                bool pick_to_size,
877                                bool pick_to_presence,
878                                bool pick_to_color,
879                                bool pick_to_opacity,
880                                bool invert_picked,
881                                double gamma_picked ,
882                                double rand_picked)
883 {
884     bool did = false;
885 
886     {
887         SPBox3D *box = dynamic_cast<SPBox3D *>(item);
888         if (box) {
889             // convert 3D boxes to ordinary groups before spraying their shapes
890             item = box->convert_to_group();
891             set->add(item);
892         }
893     }
894 
895     double _fid = g_random_double_range(0, 1);
896     double angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI );
897     double _scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 );
898     if(usepressurescale){
899         _scale = pressure;
900     }
901     double dr; double dp;
902     random_position( dr, dp, mean, standard_deviation, _distrib );
903     dr=dr*radius;
904 
905     if (mode == SPRAY_MODE_COPY || mode == SPRAY_MODE_ERASER) {
906         Geom::OptRect a = item->documentVisualBounds();
907         if (a) {
908             if(_fid <= population)
909             {
910                 SPDocument *doc = item->document;
911                 gchar const * spray_origin;
912                 if(!item->getAttribute("inkscape:spray-origin")){
913                     spray_origin = g_strdup_printf("#%s", item->getId());
914                 } else {
915                     spray_origin = item->getAttribute("inkscape:spray-origin");
916                 }
917                 Geom::Point center = item->getCenter();
918                 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-a->midpoint());
919                 SPCSSAttr *css = sp_repr_css_attr_new();
920                 if(mode == SPRAY_MODE_ERASER || no_overlap || picker || !over_transparent || !over_no_transparent){
921                     if(!fit_item(desktop
922                                  , item
923                                  , a
924                                  , move
925                                  , center
926                                  , mode
927                                  , angle
928                                  , _scale
929                                  , scale
930                                  , picker
931                                  , pick_center
932                                  , pick_inverse_value
933                                  , pick_fill
934                                  , pick_stroke
935                                  , pick_no_overlap
936                                  , over_no_transparent
937                                  , over_transparent
938                                  , no_overlap
939                                  , offset
940                                  , css
941                                  , false
942                                  , pick
943                                  , do_trace
944                                  , pick_to_size
945                                  , pick_to_presence
946                                  , pick_to_color
947                                  , pick_to_opacity
948                                  , invert_picked
949                                  , gamma_picked
950                                  , rand_picked)){
951                         return false;
952                     }
953                 }
954                 SPItem *item_copied;
955                 // Duplicate
956                 Inkscape::XML::Document* xml_doc = doc->getReprDoc();
957                 Inkscape::XML::Node *old_repr = item->getRepr();
958                 Inkscape::XML::Node *parent = old_repr->parent();
959                 Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
960                 if(!copy->attribute("inkscape:spray-origin")){
961                     copy->setAttribute("inkscape:spray-origin", spray_origin);
962                 }
963                 parent->appendChild(copy);
964                 SPObject *new_obj = doc->getObjectByRepr(copy);
965                 item_copied = dynamic_cast<SPItem *>(new_obj);   // Conversion object->item
966                 sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(_scale));
967                 sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(scale));
968                 sp_spray_rotate_rel(center,desktop,item_copied, Geom::Rotate(angle));
969                 // Move the cursor p
970                 item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
971                 Inkscape::GC::release(copy);
972                 if(picker){
973                     sp_desktop_apply_css_recursive(item_copied, css, true);
974                 }
975                 did = true;
976             }
977         }
978 #ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
979     } else if (mode == SPRAY_MODE_SINGLE_PATH) {
980         long setSize = boost::distance(set->items());
981         SPItem *parent_item = setSize > 0 ? set->items().front() : nullptr;    // Initial object
982         SPItem *unionResult = setSize > 1 ? set->items().back() : nullptr;    // Previous union
983         SPItem *item_copied = nullptr;    // Projected object
984 
985         if (parent_item) {
986             SPDocument *doc = parent_item->document;
987             Inkscape::XML::Document* xml_doc = doc->getReprDoc();
988             Inkscape::XML::Node *old_repr = parent_item->getRepr();
989             Inkscape::XML::Node *parent = old_repr->parent();
990 
991             Geom::OptRect a = parent_item->documentVisualBounds();
992             if (a) {
993                 if (_fid <= population) { // Rules the population of objects sprayed
994                     // Duplicates the parent item
995                     Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
996                     gchar const * spray_origin;
997                     if(!copy->attribute("inkscape:spray-origin")){
998                         spray_origin = g_strdup_printf("#%s", old_repr->attribute("id"));
999                         copy->setAttribute("inkscape:spray-origin", spray_origin);
1000                     } else {
1001                         spray_origin = copy->attribute("inkscape:spray-origin");
1002                     }
1003                     parent->appendChild(copy);
1004                     SPObject *new_obj = doc->getObjectByRepr(copy);
1005                     item_copied = dynamic_cast<SPItem *>(new_obj);
1006 
1007                     // Move around the cursor
1008                     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-a->midpoint());
1009 
1010                     Geom::Point center = parent_item->getCenter();
1011                     sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
1012                     sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
1013                     sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
1014                     item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
1015 
1016                     // Union and duplication
1017                     ObjectSet object_set_tmp = *desktop->getSelection();
1018                     object_set_tmp.clear();
1019                     object_set_tmp.add(item_copied);
1020                     if (unionResult) { // No need to add the very first item (initialized with NULL).
1021                         object_set_tmp.add(unionResult);
1022                     }
1023                     object_set_tmp.pathUnion(true);
1024                     set->add(parent_item);
1025                     std::vector<SPItem*> tmpitems(object_set_tmp.items().begin(), object_set_tmp.items().end());
1026                     for (auto item : tmpitems) {
1027                         set->add(item);
1028                     }
1029                     object_set_tmp.clear();
1030                     tmpitems.clear();
1031                     Inkscape::GC::release(copy);
1032                     did = true;
1033                 }
1034             }
1035         }
1036 #endif
1037     } else if (mode == SPRAY_MODE_CLONE) {
1038         Geom::OptRect a = item->documentVisualBounds();
1039         if (a) {
1040             if(_fid <= population) {
1041                 SPDocument *doc = item->document;
1042                 gchar const * spray_origin;
1043                 if(!item->getAttribute("inkscape:spray-origin")){
1044                     spray_origin = g_strdup_printf("#%s", item->getId());
1045                 } else {
1046                     spray_origin = item->getAttribute("inkscape:spray-origin");
1047                 }
1048                 Geom::Point center=item->getCenter();
1049                 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-a->midpoint());
1050                 SPCSSAttr *css = sp_repr_css_attr_new();
1051                 if(mode == SPRAY_MODE_ERASER || no_overlap || picker || !over_transparent || !over_no_transparent){
1052                     if(!fit_item(desktop
1053                                  , item
1054                                  , a
1055                                  , move
1056                                  , center
1057                                  , mode
1058                                  , angle
1059                                  , _scale
1060                                  , scale
1061                                  , picker
1062                                  , pick_center
1063                                  , pick_inverse_value
1064                                  , pick_fill
1065                                  , pick_stroke
1066                                  , pick_no_overlap
1067                                  , over_no_transparent
1068                                  , over_transparent
1069                                  , no_overlap
1070                                  , offset
1071                                  , css
1072                                  , true
1073                                  , pick
1074                                  , do_trace
1075                                  , pick_to_size
1076                                  , pick_to_presence
1077                                  , pick_to_color
1078                                  , pick_to_opacity
1079                                  , invert_picked
1080                                  , gamma_picked
1081                                  , rand_picked))
1082                     {
1083                         return false;
1084                     }
1085                 }
1086                 SPItem *item_copied;
1087                 Inkscape::XML::Document* xml_doc = doc->getReprDoc();
1088                 Inkscape::XML::Node *old_repr = item->getRepr();
1089                 Inkscape::XML::Node *parent = old_repr->parent();
1090 
1091                 // Creation of the clone
1092                 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
1093                 // Ad the clone to the list of the parent's children
1094                 parent->appendChild(clone);
1095                 // Generates the link between parent and child attributes
1096                 if(!clone->attribute("inkscape:spray-origin")){
1097                     clone->setAttribute("inkscape:spray-origin", spray_origin);
1098                 }
1099                 gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id"));
1100                 clone->setAttribute("xlink:href", href_str);
1101                 g_free(href_str);
1102 
1103                 SPObject *clone_object = doc->getObjectByRepr(clone);
1104                 // Conversion object->item
1105                 item_copied = dynamic_cast<SPItem *>(clone_object);
1106                 sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
1107                 sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
1108                 sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
1109                 item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
1110                 if(picker){
1111                     sp_desktop_apply_css_recursive(item_copied, css, true);
1112                 }
1113                 Inkscape::GC::release(clone);
1114                 did = true;
1115             }
1116         }
1117     }
1118 
1119     return did;
1120 }
1121 
sp_spray_dilate(SprayTool * tc,Geom::Point,Geom::Point p,Geom::Point vector,bool reverse)1122 static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse)
1123 {
1124     SPDesktop *desktop = tc->getDesktop();
1125     Inkscape::ObjectSet *set = tc->objectSet();
1126     if (set->isEmpty()) {
1127         return false;
1128     }
1129 
1130     bool did = false;
1131     double radius = get_dilate_radius(tc);
1132     double population = get_population(tc);
1133     if (radius == 0 || population == 0) {
1134         return false;
1135     }
1136     double path_mean = get_path_mean(tc);
1137     if (radius == 0 || path_mean == 0) {
1138         return false;
1139     }
1140     double path_standard_deviation = get_path_standard_deviation(tc);
1141     if (radius == 0 || path_standard_deviation == 0) {
1142         return false;
1143     }
1144     double move_mean = get_move_mean(tc);
1145     double move_standard_deviation = get_move_standard_deviation(tc);
1146 
1147     {
1148         std::vector<SPItem*> const items(set->items().begin(), set->items().end());
1149 
1150         for(auto item : items){
1151             g_assert(item != nullptr);
1152             sp_object_ref(item);
1153         }
1154 
1155         for(auto item : items){
1156             g_assert(item != nullptr);
1157             if (sp_spray_recursive(desktop
1158                                 , set
1159                                 , item
1160                                 , p, vector
1161                                 , tc->mode
1162                                 , radius
1163                                 , population
1164                                 , tc->scale
1165                                 , tc->scale_variation
1166                                 , reverse
1167                                 , move_mean
1168                                 , move_standard_deviation
1169                                 , tc->ratio
1170                                 , tc->tilt
1171                                 , tc->rotation_variation
1172                                 , tc->distrib
1173                                 , tc->no_overlap
1174                                 , tc->picker
1175                                 , tc->pick_center
1176                                 , tc->pick_inverse_value
1177                                 , tc->pick_fill
1178                                 , tc->pick_stroke
1179                                 , tc->pick_no_overlap
1180                                 , tc->over_no_transparent
1181                                 , tc->over_transparent
1182                                 , tc->offset
1183                                 , tc->usepressurescale
1184                                 , get_pressure(tc)
1185                                 , tc->pick
1186                                 , tc->do_trace
1187                                 , tc->pick_to_size
1188                                 , tc->pick_to_presence
1189                                 , tc->pick_to_color
1190                                 , tc->pick_to_opacity
1191                                 , tc->invert_picked
1192                                 , tc->gamma_picked
1193                                 , tc->rand_picked)) {
1194                 did = true;
1195             }
1196         }
1197 
1198         for(auto item : items){
1199             g_assert(item != nullptr);
1200             sp_object_unref(item);
1201         }
1202     }
1203 
1204     return did;
1205 }
1206 
sp_spray_update_area(SprayTool * tc)1207 static void sp_spray_update_area(SprayTool *tc)
1208 {
1209     double radius = get_dilate_radius(tc);
1210     Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) *
1211                             Geom::Rotate(tc->tilt) *
1212                             Geom::Translate(tc->getDesktop()->point()));
1213 
1214     Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); // Unit circle centered at origin.
1215     path *= sm;
1216     tc->dilate_area->set_bpath(path);
1217     tc->dilate_area->show();
1218 }
1219 
sp_spray_switch_mode(SprayTool * tc,gint mode,bool with_shift)1220 static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift)
1221 {
1222     // Select the button mode
1223     auto tb = dynamic_cast<UI::Toolbar::SprayToolbar*>(tc->getDesktop()->get_toolbar_by_name("SprayToolbar"));
1224 
1225     if(tb) {
1226         tb->set_mode(mode);
1227     } else {
1228         std::cerr << "Could not access Spray toolbar" << std::endl;
1229     }
1230 
1231     // Need to set explicitly, because the prefs may not have changed by the previous
1232     tc->mode = mode;
1233     tc->update_cursor(with_shift);
1234 }
1235 
root_handler(GdkEvent * event)1236 bool SprayTool::root_handler(GdkEvent* event) {
1237     gint ret = FALSE;
1238 
1239     switch (event->type) {
1240         case GDK_ENTER_NOTIFY:
1241             dilate_area->show();
1242             break;
1243         case GDK_LEAVE_NOTIFY:
1244             dilate_area->hide();
1245             break;
1246         case GDK_BUTTON_PRESS:
1247             if (event->button.button == 1) {
1248                 desktop->getSelection()->restoreBackup();
1249                 if (Inkscape::have_viable_layer(desktop, defaultMessageContext()) == false) {
1250                     return TRUE;
1251                 }
1252                 this->setCloneTilerPrefs();
1253                 Geom::Point const motion_w(event->button.x, event->button.y);
1254                 Geom::Point const motion_dt(desktop->w2d(motion_w));
1255                 this->last_push = desktop->dt2doc(motion_dt);
1256 
1257                 sp_spray_extinput(this, event);
1258 
1259                 forced_redraws_start(3);
1260                 set_high_motion_precision();
1261                 this->is_drawing = true;
1262                 this->is_dilating = true;
1263                 this->has_dilated = false;
1264 
1265                 object_set = *desktop->getSelection();
1266                 if (mode == SPRAY_MODE_SINGLE_PATH) {
1267                     desktop->getSelection()->clear();
1268                 }
1269 
1270                 sp_spray_dilate(this, motion_w, this->last_push, Geom::Point(0,0), MOD__SHIFT(event));
1271 
1272                 this->has_dilated = true;
1273                 ret = TRUE;
1274             }
1275             break;
1276         case GDK_MOTION_NOTIFY: {
1277             Geom::Point const motion_w(event->motion.x,
1278                                      event->motion.y);
1279             Geom::Point motion_dt(desktop->w2d(motion_w));
1280             Geom::Point motion_doc(desktop->dt2doc(motion_dt));
1281             sp_spray_extinput(this, event);
1282 
1283             // Draw the dilating cursor
1284             double radius = get_dilate_radius(this);
1285             Geom::Affine const sm (Geom::Scale(radius/(1-this->ratio), radius/(1+this->ratio)) *
1286                                    Geom::Rotate(this->tilt) *
1287                                    Geom::Translate(desktop->w2d(motion_w)));
1288 
1289             Geom::PathVector path = Geom::Path(Geom::Circle(0, 0, 1)); // Unit circle centered at origin.
1290             path *= sm;
1291             this->dilate_area->set_bpath(path);
1292             this->dilate_area->show();
1293 
1294             guint num = 0;
1295             if (!desktop->selection->isEmpty()) {
1296                 num = (guint) boost::distance(desktop->selection->items());
1297             }
1298             if (num == 0) {
1299                 this->message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray."));
1300             }
1301 
1302             // Dilating:
1303             if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
1304                 sp_spray_dilate(this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
1305                 //this->last_push = motion_doc;
1306                 this->has_dilated = true;
1307 
1308                 // It's slow, so prevent clogging up with events
1309                 gobble_motion_events(GDK_BUTTON1_MASK);
1310                 return TRUE;
1311             }
1312         }
1313         break;
1314         /* Spray with the scroll */
1315         case GDK_SCROLL: {
1316             if (event->scroll.state & GDK_BUTTON1_MASK) {
1317                 double temp ;
1318                 temp = this->population;
1319                 this->population = 1.0;
1320                 desktop->setToolboxAdjustmentValue("population", this->population * 100);
1321                 Geom::Point const scroll_w(event->button.x, event->button.y);
1322                 Geom::Point const scroll_dt = desktop->point();;
1323 
1324                 switch (event->scroll.direction) {
1325                     case GDK_SCROLL_DOWN:
1326                     case GDK_SCROLL_UP:
1327                     case GDK_SCROLL_SMOOTH: {
1328                         if (Inkscape::have_viable_layer(desktop, defaultMessageContext()) == false) {
1329                             return TRUE;
1330                         }
1331                         this->last_push = desktop->dt2doc(scroll_dt);
1332                         sp_spray_extinput(this, event);
1333                         forced_redraws_start(3);
1334                         this->is_drawing = true;
1335                         this->is_dilating = true;
1336                         this->has_dilated = false;
1337                         if(this->is_dilating) {
1338                             sp_spray_dilate(this, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false);
1339                         }
1340                         this->has_dilated = true;
1341 
1342                         this->population = temp;
1343                         desktop->setToolboxAdjustmentValue("population", this->population * 100);
1344 
1345                         ret = TRUE;
1346                     }
1347                     break;
1348                     case GDK_SCROLL_RIGHT:
1349                        {} break;
1350                     case GDK_SCROLL_LEFT:
1351                        {} break;
1352                 }
1353             }
1354             break;
1355         }
1356 
1357         case GDK_BUTTON_RELEASE: {
1358             Geom::Point const motion_w(event->button.x, event->button.y);
1359             Geom::Point const motion_dt(desktop->w2d(motion_w));
1360 
1361             forced_redraws_stop();
1362             set_high_motion_precision(false);
1363             this->is_drawing = false;
1364 
1365             if (this->is_dilating && event->button.button == 1) {
1366                 if (!this->has_dilated) {
1367                     // If we did not rub, do a light tap
1368                     this->pressure = 0.03;
1369                     sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event));
1370                 }
1371                 this->is_dilating = false;
1372                 this->has_dilated = false;
1373                 switch (this->mode) {
1374                     case SPRAY_MODE_COPY:
1375                         DocumentUndo::done(this->desktop->getDocument(),
1376                                            SP_VERB_CONTEXT_SPRAY, _("Spray with copies"));
1377                         break;
1378                     case SPRAY_MODE_CLONE:
1379                         DocumentUndo::done(this->desktop->getDocument(),
1380                                            SP_VERB_CONTEXT_SPRAY, _("Spray with clones"));
1381                         break;
1382                     case SPRAY_MODE_SINGLE_PATH:
1383                         desktop->getSelection()->add(object_set.objects().begin(), object_set.objects().end());
1384                         DocumentUndo::done(this->desktop->getDocument(),
1385                                            SP_VERB_CONTEXT_SPRAY, _("Spray in single path"));
1386                         break;
1387                 }
1388             }
1389             desktop->getSelection()->clear();
1390             object_set.clear();
1391             break;
1392         }
1393 
1394         case GDK_KEY_PRESS:
1395             switch (get_latin_keyval (&event->key)) {
1396                 case GDK_KEY_j:
1397                 case GDK_KEY_J:
1398                     if (MOD__SHIFT_ONLY(event)) {
1399                         sp_spray_switch_mode(this, SPRAY_MODE_COPY, MOD__SHIFT(event));
1400                         ret = TRUE;
1401                     }
1402                     break;
1403                 case GDK_KEY_k:
1404                 case GDK_KEY_K:
1405                     if (MOD__SHIFT_ONLY(event)) {
1406                         sp_spray_switch_mode(this, SPRAY_MODE_CLONE, MOD__SHIFT(event));
1407                         ret = TRUE;
1408                     }
1409                     break;
1410 #ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
1411                 case GDK_KEY_l:
1412                 case GDK_KEY_L:
1413                     if (MOD__SHIFT_ONLY(event)) {
1414                         sp_spray_switch_mode(this, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT(event));
1415                         ret = TRUE;
1416                     }
1417                     break;
1418 #endif
1419                 case GDK_KEY_Up:
1420                 case GDK_KEY_KP_Up:
1421                     if (!MOD__CTRL_ONLY(event)) {
1422                         this->population += 0.01;
1423                         if (this->population > 1.0) {
1424                             this->population = 1.0;
1425                         }
1426                         desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
1427                         ret = TRUE;
1428                     }
1429                     break;
1430                 case GDK_KEY_Down:
1431                 case GDK_KEY_KP_Down:
1432                     if (!MOD__CTRL_ONLY(event)) {
1433                         this->population -= 0.01;
1434                         if (this->population < 0.0) {
1435                             this->population = 0.0;
1436                         }
1437                         desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
1438                         ret = TRUE;
1439                     }
1440                     break;
1441                 case GDK_KEY_Right:
1442                 case GDK_KEY_KP_Right:
1443                     if (!MOD__CTRL_ONLY(event)) {
1444                         this->width += 0.01;
1445                         if (this->width > 1.0) {
1446                             this->width = 1.0;
1447                         }
1448                         // The same spinbutton is for alt+x
1449                         desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
1450                         sp_spray_update_area(this);
1451                         ret = TRUE;
1452                     }
1453                     break;
1454                 case GDK_KEY_Left:
1455                 case GDK_KEY_KP_Left:
1456                     if (!MOD__CTRL_ONLY(event)) {
1457                         this->width -= 0.01;
1458                         if (this->width < 0.01) {
1459                             this->width = 0.01;
1460                         }
1461                         desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
1462                         sp_spray_update_area(this);
1463                         ret = TRUE;
1464                     }
1465                     break;
1466                 case GDK_KEY_Home:
1467                 case GDK_KEY_KP_Home:
1468                     this->width = 0.01;
1469                     desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
1470                     sp_spray_update_area(this);
1471                     ret = TRUE;
1472                     break;
1473                 case GDK_KEY_End:
1474                 case GDK_KEY_KP_End:
1475                     this->width = 1.0;
1476                     desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
1477                     sp_spray_update_area(this);
1478                     ret = TRUE;
1479                     break;
1480                 case GDK_KEY_x:
1481                 case GDK_KEY_X:
1482                     if (MOD__ALT_ONLY(event)) {
1483                         desktop->setToolboxFocusTo("spray-width");
1484                         ret = TRUE;
1485                     }
1486                     break;
1487                 case GDK_KEY_Shift_L:
1488                 case GDK_KEY_Shift_R:
1489                     this->update_cursor(true);
1490                     break;
1491                 case GDK_KEY_Control_L:
1492                 case GDK_KEY_Control_R:
1493                     break;
1494                 case GDK_KEY_Delete:
1495                 case GDK_KEY_KP_Delete:
1496                 case GDK_KEY_BackSpace:
1497                     ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
1498                     break;
1499 
1500                 default:
1501                     break;
1502             }
1503             break;
1504 
1505         case GDK_KEY_RELEASE: {
1506             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1507             switch (get_latin_keyval(&event->key)) {
1508                 case GDK_KEY_Shift_L:
1509                 case GDK_KEY_Shift_R:
1510                     this->update_cursor(false);
1511                     break;
1512                 case GDK_KEY_Control_L:
1513                 case GDK_KEY_Control_R:
1514                     sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
1515                     this->message_context->clear();
1516                     break;
1517                 default:
1518                     sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
1519                     break;
1520             }
1521         }
1522 
1523         default:
1524             break;
1525     }
1526 
1527     if (!ret) {
1528 //        if ((SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler) {
1529 //            ret = (SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler(event_context, event);
1530 //        }
1531         ret = ToolBase::root_handler(event);
1532     }
1533 
1534     return ret;
1535 }
1536 
1537 }
1538 }
1539 }
1540 
1541 /*
1542   Local Variables:
1543   mode:c++
1544   c-file-style:"stroustrup"
1545   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1546   indent-tabs-mode:nil
1547   fill-column:99
1548   End:
1549 */
1550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1551 
1552