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