summaryrefslogtreecommitdiffstats
path: root/src/ui/tools/spray-tool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/tools/spray-tool.cpp')
-rw-r--r--src/ui/tools/spray-tool.cpp1538
1 files changed, 1538 insertions, 0 deletions
diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp
new file mode 100644
index 0000000..705b129
--- /dev/null
+++ b/src/ui/tools/spray-tool.cpp
@@ -0,0 +1,1538 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Spray Tool
+ *
+ * Authors:
+ * Pierre-Antoine MARC
+ * Pierre CACLIN
+ * Aurel-Aimé MARMION
+ * Julien LERAY
+ * Benoît LAVORATA
+ * Vincent MONTAGNE
+ * Pierre BARBRY-BLOT
+ * Steren GIANNINI (steren.giannini@gmail.com)
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * Jabiertxo Arraiza <jabier.arraiza@marker.es>
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2009 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <numeric>
+#include <vector>
+#include <tuple>
+
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+
+#include <2geom/circle.h>
+
+
+#include "context-fns.h"
+#include "desktop-events.h"
+#include "desktop-style.h"
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "filter-chemistry.h"
+#include "inkscape.h"
+#include "include/macros.h"
+#include "message-context.h"
+#include "path-chemistry.h"
+#include "selection.h"
+
+#include "display/cairo-utils.h"
+#include "display/curve.h"
+#include "display/drawing-context.h"
+#include "display/drawing.h"
+#include "display/control/canvas-item-bpath.h"
+#include "display/control/canvas-item-drawing.h"
+
+#include "object/box3d.h"
+#include "object/sp-use.h"
+#include "object/sp-item-transform.h"
+
+#include "svg/svg.h"
+#include "svg/svg-color.h"
+
+#include "ui/icon-names.h"
+#include "ui/toolbar/spray-toolbar.h"
+#include "ui/tools/spray-tool.h"
+
+
+using Inkscape::DocumentUndo;
+
+#define DDC_RED_RGBA 0xff0000ff
+#define DYNA_MIN_WIDTH 1.0e-6
+
+// Disabled in 0.91 because of Bug #1274831 (crash, spraying an object
+// with the mode: spray object in single path)
+// Please enable again when working on 1.0
+#define ENABLE_SPRAY_MODE_SINGLE_PATH
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+enum {
+ PICK_COLOR,
+ PICK_OPACITY,
+ PICK_R,
+ PICK_G,
+ PICK_B,
+ PICK_H,
+ PICK_S,
+ PICK_L
+};
+
+/**
+ * This function returns pseudo-random numbers from a normal distribution
+ * @param mu : mean
+ * @param sigma : standard deviation ( > 0 )
+ */
+inline double NormalDistribution(double mu, double sigma)
+{
+ // use Box Muller's algorithm
+ return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) );
+}
+
+/* Method to rotate items */
+static void sp_spray_rotate_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Rotate const &rotation)
+{
+ Geom::Translate const s(c);
+ Geom::Affine affine = s.inverse() * rotation * s;
+ // Rotate item.
+ item->set_i2d_affine(item->i2dt_affine() * affine);
+ // Use each item's own transform writer, consistent with sp_selection_apply_affine()
+ item->doWriteTransform(item->transform);
+ // Restore the center position (it's changed because the bbox center changed)
+ if (item->isCenterSet()) {
+ item->setCenter(c);
+ item->updateRepr();
+ }
+}
+
+/* Method to scale items */
+static void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale)
+{
+ Geom::Translate const s(c);
+ item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s);
+ item->doWriteTransform(item->transform);
+}
+
+SprayTool::SprayTool(SPDesktop *desktop)
+ : ToolBase(desktop, "/tools/spray", "spray.svg", false)
+ , pressure(TC_DEFAULT_PRESSURE)
+ , dragging(false)
+ , usepressurewidth(false)
+ , usepressurepopulation(false)
+ , usepressurescale(false)
+ , usetilt(false)
+ , usetext(false)
+ , width(0.2)
+ , ratio(0)
+ , tilt(0)
+ , rotation_variation(0)
+ , population(0)
+ , scale_variation(1)
+ , scale(1)
+ , mean(0.2)
+ , standard_deviation(0.2)
+ , distrib(1)
+ , mode(0)
+ , is_drawing(false)
+ , is_dilating(false)
+ , has_dilated(false)
+ , dilate_area(nullptr)
+ , no_overlap(false)
+ , picker(false)
+ , pick_center(true)
+ , pick_inverse_value(false)
+ , pick_fill(false)
+ , pick_stroke(false)
+ , pick_no_overlap(false)
+ , over_transparent(true)
+ , over_no_transparent(true)
+ , offset(0)
+ , pick(0)
+ , do_trace(false)
+ , pick_to_size(false)
+ , pick_to_presence(false)
+ , pick_to_color(false)
+ , pick_to_opacity(false)
+ , invert_picked(false)
+ , gamma_picked(0)
+ , rand_picked(0)
+{
+ dilate_area = new Inkscape::CanvasItemBpath(desktop->getCanvasControls());
+ dilate_area->set_stroke(0xff9900ff);
+ dilate_area->set_fill(0x0, SP_WIND_RULE_EVENODD);
+ dilate_area->hide();
+
+ this->is_drawing = false;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/dialogs/clonetiler/dotrace", false);
+ if (prefs->getBool("/tools/spray/selcue")) {
+ this->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/spray/gradientdrag")) {
+ this->enableGrDrag();
+ }
+ desktop->getSelection()->setBackup();
+ sp_event_context_read(this, "distrib");
+ sp_event_context_read(this, "width");
+ sp_event_context_read(this, "ratio");
+ sp_event_context_read(this, "tilt");
+ sp_event_context_read(this, "rotation_variation");
+ sp_event_context_read(this, "scale_variation");
+ sp_event_context_read(this, "mode");
+ sp_event_context_read(this, "population");
+ sp_event_context_read(this, "mean");
+ sp_event_context_read(this, "standard_deviation");
+ sp_event_context_read(this, "usepressurewidth");
+ sp_event_context_read(this, "usepressurepopulation");
+ sp_event_context_read(this, "usepressurescale");
+ sp_event_context_read(this, "Scale");
+ sp_event_context_read(this, "offset");
+ sp_event_context_read(this, "picker");
+ sp_event_context_read(this, "pick_center");
+ sp_event_context_read(this, "pick_inverse_value");
+ sp_event_context_read(this, "pick_fill");
+ sp_event_context_read(this, "pick_stroke");
+ sp_event_context_read(this, "pick_no_overlap");
+ sp_event_context_read(this, "over_no_transparent");
+ sp_event_context_read(this, "over_transparent");
+ sp_event_context_read(this, "no_overlap");
+}
+
+SprayTool::~SprayTool() {
+ if (!object_set.isEmpty()) {
+ object_set.clear();
+ }
+ _desktop->getSelection()->restoreBackup();
+ this->enableGrDrag(false);
+ this->style_set_connection.disconnect();
+
+ if (this->dilate_area) {
+ delete this->dilate_area;
+ this->dilate_area = nullptr;
+ }
+}
+
+void SprayTool::update_cursor(bool /*with_shift*/) {
+ guint num = 0;
+ gchar *sel_message = nullptr;
+
+ if (!_desktop->selection->isEmpty()) {
+ num = (guint)boost::distance(_desktop->selection->items());
+ sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
+ } else {
+ sel_message = g_strdup_printf("%s", _("<b>Nothing</b> selected"));
+ }
+
+ switch (this->mode) {
+ case SPRAY_MODE_COPY:
+ 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);
+ break;
+ case SPRAY_MODE_CLONE:
+ 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);
+ break;
+ case SPRAY_MODE_SINGLE_PATH:
+ 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);
+ break;
+ default:
+ break;
+ }
+ g_free(sel_message);
+}
+
+
+void SprayTool::setCloneTilerPrefs() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->do_trace = prefs->getBool("/dialogs/clonetiler/dotrace", false);
+ this->pick = prefs->getInt("/dialogs/clonetiler/pick");
+ this->pick_to_size = prefs->getBool("/dialogs/clonetiler/pick_to_size", false);
+ this->pick_to_presence = prefs->getBool("/dialogs/clonetiler/pick_to_presence", false);
+ this->pick_to_color = prefs->getBool("/dialogs/clonetiler/pick_to_color", false);
+ this->pick_to_opacity = prefs->getBool("/dialogs/clonetiler/pick_to_opacity", false);
+ this->rand_picked = 0.01 * prefs->getDoubleLimited("/dialogs/clonetiler/rand_picked", 0, 0, 100);
+ this->invert_picked = prefs->getBool("/dialogs/clonetiler/invert_picked", false);
+ this->gamma_picked = prefs->getDoubleLimited("/dialogs/clonetiler/gamma_picked", 0, -10, 10);
+}
+
+void SprayTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "mode") {
+ this->mode = val.getInt();
+ this->update_cursor(false);
+ } else if (path == "width") {
+ this->width = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "usepressurewidth") {
+ this->usepressurewidth = val.getBool();
+ } else if (path == "usepressurepopulation") {
+ this->usepressurepopulation = val.getBool();
+ } else if (path == "usepressurescale") {
+ this->usepressurescale = val.getBool();
+ } else if (path == "population") {
+ this->population = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "rotation_variation") {
+ this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0);
+ } else if (path == "scale_variation") {
+ this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0);
+ } else if (path == "standard_deviation") {
+ this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "mean") {
+ this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100);
+// Not implemented in the toolbar and preferences yet
+ } else if (path == "distribution") {
+ this->distrib = val.getInt(1);
+ } else if (path == "tilt") {
+ this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0);
+ } else if (path == "ratio") {
+ this->ratio = CLAMP(val.getDouble(), 0.0, 0.9);
+ } else if (path == "offset") {
+ this->offset = val.getDoubleLimited(100.0, 0, 1000.0);
+ } else if (path == "pick_center") {
+ this->pick_center = val.getBool(true);
+ } else if (path == "pick_inverse_value") {
+ this->pick_inverse_value = val.getBool(false);
+ } else if (path == "pick_fill") {
+ this->pick_fill = val.getBool(false);
+ } else if (path == "pick_stroke") {
+ this->pick_stroke = val.getBool(false);
+ } else if (path == "pick_no_overlap") {
+ this->pick_no_overlap = val.getBool(false);
+ } else if (path == "over_no_transparent") {
+ this->over_no_transparent = val.getBool(true);
+ } else if (path == "over_transparent") {
+ this->over_transparent = val.getBool(true);
+ } else if (path == "no_overlap") {
+ this->no_overlap = val.getBool(false);
+ } else if (path == "picker") {
+ this->picker = val.getBool(false);
+ }
+}
+
+static void sp_spray_extinput(SprayTool *tc, GdkEvent *event)
+{
+ if (gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &tc->pressure)) {
+ tc->pressure = CLAMP(tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
+ } else {
+ tc->pressure = TC_DEFAULT_PRESSURE;
+ }
+}
+
+static double get_width(SprayTool *tc)
+{
+ double pressure = (tc->usepressurewidth? tc->pressure / TC_DEFAULT_PRESSURE : 1);
+ return pressure * tc->width;
+}
+
+static double get_dilate_radius(SprayTool *tc)
+{
+ return 250 * get_width(tc)/tc->getDesktop()->current_zoom();
+}
+
+static double get_path_mean(SprayTool *tc)
+{
+ return tc->mean;
+}
+
+static double get_path_standard_deviation(SprayTool *tc)
+{
+ return tc->standard_deviation;
+}
+
+static double get_population(SprayTool *tc)
+{
+ double pressure = (tc->usepressurepopulation? tc->pressure / TC_DEFAULT_PRESSURE : 1);
+ return pressure * tc->population;
+}
+
+static double get_pressure(SprayTool *tc)
+{
+ double pressure = tc->pressure / TC_DEFAULT_PRESSURE;
+ return pressure;
+}
+
+static double get_move_mean(SprayTool *tc)
+{
+ return tc->mean;
+}
+
+static double get_move_standard_deviation(SprayTool *tc)
+{
+ return tc->standard_deviation;
+}
+
+/**
+ * Method to handle the distribution of the items
+ * @param[out] radius : radius of the position of the sprayed object
+ * @param[out] angle : angle of the position of the sprayed object
+ * @param[in] a : mean
+ * @param[in] s : standard deviation
+ * @param[in] choice :
+
+ */
+static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/)
+{
+ // angle is taken from an uniform distribution
+ angle = g_random_double_range(0, M_PI*2.0);
+
+ // radius is taken from a Normal Distribution
+ double radius_temp =-1;
+ while(!((radius_temp >= 0) && (radius_temp <=1 )))
+ {
+ radius_temp = NormalDistribution(a, s);
+ }
+ // Because we are in polar coordinates, a special treatment has to be done to the radius.
+ // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to
+ // be uniformily distributed on the disk (more at the center and less at the boundary).
+ // We counter this effect with a 0.5 exponent. This is empiric.
+ radius = pow(radius_temp, 0.5);
+
+}
+
+static void sp_spray_transform_path(SPItem * item, Geom::Path &path, Geom::Affine affine, Geom::Point center){
+ path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr).inverse();
+ path *= item->transform.inverse();
+ Geom::Affine dt2p;
+ if (item->parent) {
+ dt2p = static_cast<SPItem *>(item->parent)->i2dt_affine().inverse();
+ } else {
+ dt2p = item->document->dt2doc();
+ }
+ Geom::Affine i2dt = item->i2dt_affine() * Geom::Translate(center).inverse() * affine * Geom::Translate(center);
+ path *= i2dt * dt2p;
+ path *= i2anc_affine(static_cast<SPItem *>(item->parent), nullptr);
+}
+
+/**
+Randomizes \a val by \a rand, with 0 < val < 1 and all values (including 0, 1) having the same
+probability of being displaced.
+ */
+double randomize01(double val, double rand)
+{
+ double base = MIN (val - rand, 1 - 2*rand);
+ if (base < 0) {
+ base = 0;
+ }
+ val = base + g_random_double_range (0, MIN (2 * rand, 1 - base));
+ return CLAMP(val, 0, 1); // this should be unnecessary with the above provisions, but just in case...
+}
+
+static guint32 getPickerData(Geom::IntRect area, SPDesktop *desktop)
+{
+ Inkscape::CanvasItemDrawing *canvas_item_drawing = desktop->getCanvasDrawing();
+ Inkscape::Drawing *drawing = canvas_item_drawing->get_drawing();
+
+ // Ensure drawing up-to-date. (Is this really necessary?)
+ drawing->update();
+
+ // Get average color.
+ double R, G, B, A;
+ drawing->average_color(area, R, G, B, A);
+
+ //this can fix the bug #1511998 if confirmed
+ if ( A < 1e-6) {
+ R = 1.0;
+ G = 1.0;
+ B = 1.0;
+ }
+
+ return SP_RGBA32_F_COMPOSE(R, G, B, A);
+}
+
+static void showHidden(std::vector<SPItem *> items_down){
+ for (auto item_hidden : items_down) {
+ item_hidden->setHidden(false);
+ item_hidden->updateRepr();
+ }
+}
+//todo: maybe move same parameter to preferences
+static bool 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)
+{
+ SPDocument *doc = item->document;
+ double width = bbox->width();
+ double height = bbox->height();
+ double offset_width = (offset * width)/100.0 - (width);
+ if(offset_width < 0 ){
+ offset_width = 0;
+ }
+ double offset_height = (offset * height)/100.0 - (height);
+ if(offset_height < 0 ){
+ offset_height = 0;
+ }
+ if(picker && pick_to_size && !trace_scale && do_trace){
+ _scale = 0.1;
+ }
+ 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));
+ Geom::Path path;
+ path.start(Geom::Point(bbox_procesed->left(), bbox_procesed->top()));
+ path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->top()));
+ path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->right(), bbox_procesed->bottom()));
+ path.appendNew<Geom::LineSegment>(Geom::Point(bbox_procesed->left(), bbox_procesed->bottom()));
+ path.close(true);
+ sp_spray_transform_path(item, path, Geom::Scale(_scale), center);
+ sp_spray_transform_path(item, path, Geom::Scale(scale), center);
+ sp_spray_transform_path(item, path, Geom::Rotate(angle), center);
+ path *= Geom::Translate(move);
+ path *= desktop->doc2dt();
+ bbox_procesed = path.boundsFast();
+ double bbox_left_main = bbox_procesed->left();
+ double bbox_right_main = bbox_procesed->right();
+ double bbox_top_main = bbox_procesed->top();
+ double bbox_bottom_main = bbox_procesed->bottom();
+ double width_transformed = bbox_procesed->width();
+ double height_transformed = bbox_procesed->height();
+ Geom::Point mid_point = desktop->d2w(bbox_procesed->midpoint());
+ Geom::IntRect area = Geom::IntRect::from_xywh(floor(mid_point[Geom::X]), floor(mid_point[Geom::Y]), 1, 1);
+ guint32 rgba = getPickerData(area, desktop);
+ guint32 rgba2 = 0xffffff00;
+ Geom::Rect rect_sprayed(desktop->d2w(Geom::Point(bbox_left_main,bbox_top_main)), desktop->d2w(Geom::Point(bbox_right_main,bbox_bottom_main)));
+ if (!rect_sprayed.hasZeroArea()) {
+ rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
+ }
+ if(pick_no_overlap) {
+ if(rgba != rgba2) {
+ if(mode != SPRAY_MODE_ERASER) {
+ return false;
+ }
+ }
+ }
+ if(!pick_center) {
+ rgba = rgba2;
+ }
+ if(!over_transparent && (SP_RGBA32_A_F(rgba) == 0 || SP_RGBA32_A_F(rgba) < 1e-6)) {
+ if(mode != SPRAY_MODE_ERASER) {
+ return false;
+ }
+ }
+ if(!over_no_transparent && SP_RGBA32_A_F(rgba) > 0) {
+ if(mode != SPRAY_MODE_ERASER) {
+ return false;
+ }
+ }
+ if(offset < 100 ) {
+ offset_width = ((99.0 - offset) * width_transformed)/100.0 - width_transformed;
+ offset_height = ((99.0 - offset) * height_transformed)/100.0 - height_transformed;
+ } else {
+ offset_width = 0;
+ offset_height = 0;
+ }
+ std::vector<SPItem*> items_down = desktop->getDocument()->getItemsPartiallyInBox(desktop->dkey, *bbox_procesed);
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (selection->isEmpty()) {
+ return false;
+ }
+ std::vector<SPItem*> const items_selected(selection->items().begin(), selection->items().end());
+ std::vector<SPItem*> items_down_erased;
+ for (std::vector<SPItem*>::const_iterator i=items_down.begin(); i!=items_down.end(); ++i) {
+ SPItem *item_down = *i;
+ Geom::OptRect bbox_down = item_down->documentVisualBounds();
+ double bbox_left = bbox_down->left();
+ double bbox_top = bbox_down->top();
+ gchar const * item_down_sharp = g_strdup_printf("#%s", item_down->getId());
+ items_down_erased.push_back(item_down);
+ for (auto item_selected : items_selected) {
+ gchar const * spray_origin;
+ if(!item_selected->getAttribute("inkscape:spray-origin")){
+ spray_origin = g_strdup_printf("#%s", item_selected->getId());
+ } else {
+ spray_origin = item_selected->getAttribute("inkscape:spray-origin");
+ }
+ if(strcmp(item_down_sharp, spray_origin) == 0 ||
+ (item_down->getAttribute("inkscape:spray-origin") &&
+ strcmp(item_down->getAttribute("inkscape:spray-origin"),spray_origin) == 0 ))
+ {
+ if(mode == SPRAY_MODE_ERASER) {
+ if(strcmp(item_down_sharp, spray_origin) != 0 && !selection->includes(item_down) ){
+ item_down->deleteObject();
+ items_down_erased.pop_back();
+ break;
+ }
+ } else if(no_overlap) {
+ if(!(offset_width < 0 && offset_height < 0 && std::abs(bbox_left - bbox_left_main) > std::abs(offset_width) &&
+ std::abs(bbox_top - bbox_top_main) > std::abs(offset_height))){
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ } else if(picker || over_transparent || over_no_transparent) {
+ item_down->setHidden(true);
+ item_down->updateRepr();
+ }
+ }
+ }
+ }
+ if(mode == SPRAY_MODE_ERASER){
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down_erased);
+ }
+ return false;
+ }
+ if(picker || over_transparent || over_no_transparent){
+ if(!no_overlap){
+ doc->ensureUpToDate();
+ rgba = getPickerData(area, desktop);
+ if (!rect_sprayed.hasZeroArea()) {
+ rgba2 = getPickerData(rect_sprayed.roundOutwards(), desktop);
+ }
+ }
+ if(pick_no_overlap){
+ if(rgba != rgba2){
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ }
+ if(!pick_center){
+ rgba = rgba2;
+ }
+ double opacity = 1.0;
+ gchar color_string[32]; *color_string = 0;
+ float r = SP_RGBA32_R_F(rgba);
+ float g = SP_RGBA32_G_F(rgba);
+ float b = SP_RGBA32_B_F(rgba);
+ float a = SP_RGBA32_A_F(rgba);
+ if(!over_transparent && (a == 0 || a < 1e-6)){
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ if(!over_no_transparent && a > 0){
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+
+ if(picker && do_trace){
+ float hsl[3];
+ SPColor::rgb_to_hsl_floatv (hsl, r, g, b);
+
+ gdouble val = 0;
+ switch (pick) {
+ case PICK_COLOR:
+ val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max
+ break;
+ case PICK_OPACITY:
+ val = a;
+ break;
+ case PICK_R:
+ val = r;
+ break;
+ case PICK_G:
+ val = g;
+ break;
+ case PICK_B:
+ val = b;
+ break;
+ case PICK_H:
+ val = hsl[0];
+ break;
+ case PICK_S:
+ val = hsl[1];
+ break;
+ case PICK_L:
+ val = 1 - hsl[2];
+ break;
+ default:
+ break;
+ }
+
+ if (rand_picked > 0) {
+ val = randomize01 (val, rand_picked);
+ r = randomize01 (r, rand_picked);
+ g = randomize01 (g, rand_picked);
+ b = randomize01 (b, rand_picked);
+ }
+
+ if (gamma_picked != 0) {
+ double power;
+ if (gamma_picked > 0)
+ power = 1/(1 + fabs(gamma_picked));
+ else
+ power = 1 + fabs(gamma_picked);
+
+ val = pow (val, power);
+ r = pow ((double)r, (double)power);
+ g = pow ((double)g, (double)power);
+ b = pow ((double)b, (double)power);
+ }
+
+ if (invert_picked) {
+ val = 1 - val;
+ r = 1 - r;
+ g = 1 - g;
+ b = 1 - b;
+ }
+
+ val = CLAMP (val, 0, 1);
+ r = CLAMP (r, 0, 1);
+ g = CLAMP (g, 0, 1);
+ b = CLAMP (b, 0, 1);
+
+ // recompose tweaked color
+ rgba = SP_RGBA32_F_COMPOSE(r, g, b, a);
+ if (pick_to_size) {
+ if(!trace_scale){
+ if(pick_inverse_value) {
+ _scale = 1.0 - val;
+ } else {
+ _scale = val;
+ }
+ if(_scale == 0.0) {
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ if(!fit_item(desktop
+ , item
+ , bbox
+ , move
+ , center
+ , mode
+ , angle
+ , _scale
+ , scale
+ , picker
+ , pick_center
+ , pick_inverse_value
+ , pick_fill
+ , pick_stroke
+ , pick_no_overlap
+ , over_no_transparent
+ , over_transparent
+ , no_overlap
+ , offset
+ , css
+ , true
+ , pick
+ , do_trace
+ , pick_to_size
+ , pick_to_presence
+ , pick_to_color
+ , pick_to_opacity
+ , invert_picked
+ , gamma_picked
+ , rand_picked)
+ )
+ {
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ }
+ }
+
+ if (pick_to_opacity) {
+ if(pick_inverse_value) {
+ opacity *= 1.0 - val;
+ } else {
+ opacity *= val;
+ }
+ std::stringstream opacity_str;
+ opacity_str.imbue(std::locale::classic());
+ opacity_str << opacity;
+ sp_repr_css_set_property(css, "opacity", opacity_str.str().c_str());
+ }
+ if (pick_to_presence) {
+ if (g_random_double_range (0, 1) > val) {
+ //Hiding the element is a way to retain original
+ //behaviour of tiled clones for presence option.
+ sp_repr_css_set_property(css, "opacity", "0");
+ }
+ }
+ if (pick_to_color) {
+ sp_svg_write_color(color_string, sizeof(color_string), rgba);
+ if(pick_fill){
+ sp_repr_css_set_property(css, "fill", color_string);
+ }
+ if(pick_stroke){
+ sp_repr_css_set_property(css, "stroke", color_string);
+ }
+ }
+ if (opacity < 1e-6) { // invisibly transparent, skip
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ return false;
+ }
+ }
+ if(!do_trace){
+ if(!pick_center){
+ rgba = rgba2;
+ }
+ if (pick_inverse_value) {
+ r = 1 - SP_RGBA32_R_F(rgba);
+ g = 1 - SP_RGBA32_G_F(rgba);
+ b = 1 - SP_RGBA32_B_F(rgba);
+ } else {
+ r = SP_RGBA32_R_F(rgba);
+ g = SP_RGBA32_G_F(rgba);
+ b = SP_RGBA32_B_F(rgba);
+ }
+ rgba = SP_RGBA32_F_COMPOSE(r, g, b, a);
+ sp_svg_write_color(color_string, sizeof(color_string), rgba);
+ if(pick_fill){
+ sp_repr_css_set_property(css, "fill", color_string);
+ }
+ if(pick_stroke){
+ sp_repr_css_set_property(css, "stroke", color_string);
+ }
+ }
+ if(!no_overlap && (picker || over_transparent || over_no_transparent)){
+ showHidden(items_down);
+ }
+ }
+ return true;
+}
+
+static bool sp_spray_recursive(SPDesktop *desktop,
+ Inkscape::ObjectSet *set,
+ SPItem *item,
+ SPItem *&single_path_output,
+ Geom::Point p,
+ Geom::Point /*vector*/,
+ gint mode,
+ double radius,
+ double population,
+ double &scale,
+ double scale_variation,
+ bool /*reverse*/,
+ 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)
+{
+ bool did = false;
+
+ {
+ // convert 3D boxes to ordinary groups before spraying their shapes
+ // TODO: ideally the original object is preserved.
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ desktop->getSelection()->remove(dynamic_cast<SPObject *>(item));
+ set->remove(item);
+ item = box->convert_to_group();
+ set->add(item);
+ desktop->getSelection()->add(dynamic_cast<SPObject *>(item));
+ }
+ }
+
+ double _fid = g_random_double_range(0, 1);
+ double angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI );
+ double _scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 );
+ if(usepressurescale){
+ _scale = pressure;
+ }
+ double dr; double dp;
+ random_position( dr, dp, mean, standard_deviation, _distrib );
+ dr=dr*radius;
+
+ if (mode == SPRAY_MODE_COPY || mode == SPRAY_MODE_ERASER) {
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ if(_fid <= population)
+ {
+ SPDocument *doc = item->document;
+ gchar const * spray_origin;
+ if(!item->getAttribute("inkscape:spray-origin")){
+ spray_origin = g_strdup_printf("#%s", item->getId());
+ } else {
+ spray_origin = item->getAttribute("inkscape:spray-origin");
+ }
+ Geom::Point center = item->getCenter();
+ 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());
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (mode == SPRAY_MODE_ERASER ||
+ pick_no_overlap || no_overlap || picker ||
+ !over_transparent || !over_no_transparent) {
+ if(!fit_item(desktop
+ , item
+ , a
+ , move
+ , center
+ , mode
+ , angle
+ , _scale
+ , scale
+ , picker
+ , pick_center
+ , pick_inverse_value
+ , pick_fill
+ , pick_stroke
+ , pick_no_overlap
+ , over_no_transparent
+ , over_transparent
+ , no_overlap
+ , offset
+ , css
+ , false
+ , pick
+ , do_trace
+ , pick_to_size
+ , pick_to_presence
+ , pick_to_color
+ , pick_to_opacity
+ , invert_picked
+ , gamma_picked
+ , rand_picked)){
+ return false;
+ }
+ }
+ SPItem *item_copied;
+ // Duplicate
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+ Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
+ if(!copy->attribute("inkscape:spray-origin")){
+ copy->setAttribute("inkscape:spray-origin", spray_origin);
+ }
+ parent->appendChild(copy);
+ SPObject *new_obj = doc->getObjectByRepr(copy);
+ item_copied = dynamic_cast<SPItem *>(new_obj); // Conversion object->item
+ sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(_scale));
+ sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(scale));
+ sp_spray_rotate_rel(center,desktop,item_copied, Geom::Rotate(angle));
+ // Move the cursor p
+ item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
+ Inkscape::GC::release(copy);
+ if(picker){
+ sp_desktop_apply_css_recursive(item_copied, css, true);
+ }
+ did = true;
+ }
+ }
+#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
+ } else if (mode == SPRAY_MODE_SINGLE_PATH) {
+ if (item) {
+ SPDocument *doc = item->document;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ if (_fid <= population) { // Rules the population of objects sprayed
+ // Duplicates the parent item
+ Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
+ gchar const * spray_origin;
+ if(!copy->attribute("inkscape:spray-origin")){
+ spray_origin = g_strdup_printf("#%s", old_repr->attribute("id"));
+ } else {
+ spray_origin = copy->attribute("inkscape:spray-origin");
+ }
+ parent->appendChild(copy);
+ SPObject *new_obj = doc->getObjectByRepr(copy);
+ SPItem *item_copied = dynamic_cast<SPItem *>(new_obj);
+
+ // Move around the cursor
+ 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());
+
+ Geom::Point center = item->getCenter();
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
+ sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
+ item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
+
+ // Union
+ // only works if no groups in selection
+ ObjectSet object_set_tmp = *desktop->getSelection();
+ object_set_tmp.clear();
+ object_set_tmp.add(item_copied);
+ object_set_tmp.removeLPESRecursive(true);
+ if (dynamic_cast<SPUse*>(object_set_tmp.objects().front())) {
+ object_set_tmp.unlinkRecursive(true);
+ }
+ if (single_path_output) { // Previous result
+ object_set_tmp.add(single_path_output);
+ }
+ object_set_tmp.pathUnion(true);
+ single_path_output = object_set_tmp.items().front();
+ for (auto item : object_set_tmp.items()) {
+ auto repr = item->getRepr();
+ repr->setAttribute("inkscape:spray-origin", spray_origin);
+ }
+ object_set_tmp.clear();
+ Inkscape::GC::release(copy);
+ did = true;
+ }
+ }
+ }
+#endif
+ } else if (mode == SPRAY_MODE_CLONE) {
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ if(_fid <= population) {
+ SPDocument *doc = item->document;
+ gchar const * spray_origin;
+ if(!item->getAttribute("inkscape:spray-origin")){
+ spray_origin = g_strdup_printf("#%s", item->getId());
+ } else {
+ spray_origin = item->getAttribute("inkscape:spray-origin");
+ }
+ Geom::Point center=item->getCenter();
+ 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());
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (mode == SPRAY_MODE_ERASER ||
+ pick_no_overlap || no_overlap || picker ||
+ !over_transparent || !over_no_transparent) {
+ if(!fit_item(desktop
+ , item
+ , a
+ , move
+ , center
+ , mode
+ , angle
+ , _scale
+ , scale
+ , picker
+ , pick_center
+ , pick_inverse_value
+ , pick_fill
+ , pick_stroke
+ , pick_no_overlap
+ , over_no_transparent
+ , over_transparent
+ , no_overlap
+ , offset
+ , css
+ , true
+ , pick
+ , do_trace
+ , pick_to_size
+ , pick_to_presence
+ , pick_to_color
+ , pick_to_opacity
+ , invert_picked
+ , gamma_picked
+ , rand_picked))
+ {
+ return false;
+ }
+ }
+ SPItem *item_copied;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+
+ // Creation of the clone
+ Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
+ // Ad the clone to the list of the parent's children
+ parent->appendChild(clone);
+ // Generates the link between parent and child attributes
+ if(!clone->attribute("inkscape:spray-origin")){
+ clone->setAttribute("inkscape:spray-origin", spray_origin);
+ }
+ gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id"));
+ clone->setAttribute("xlink:href", href_str);
+ g_free(href_str);
+
+ SPObject *clone_object = doc->getObjectByRepr(clone);
+ // Conversion object->item
+ item_copied = dynamic_cast<SPItem *>(clone_object);
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
+ sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
+ item_copied->move_rel(Geom::Translate(move * desktop->doc2dt().withoutTranslation()));
+ if(picker){
+ sp_desktop_apply_css_recursive(item_copied, css, true);
+ }
+ Inkscape::GC::release(clone);
+ did = true;
+ }
+ }
+ }
+
+ return did;
+}
+
+static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse)
+{
+ SPDesktop *desktop = tc->getDesktop();
+ Inkscape::ObjectSet *set = tc->objectSet();
+ if (set->isEmpty()) {
+ return false;
+ }
+
+ bool did = false;
+ double radius = get_dilate_radius(tc);
+ double population = get_population(tc);
+ if (radius == 0 || population == 0) {
+ return false;
+ }
+ double path_mean = get_path_mean(tc);
+ if (radius == 0 || path_mean == 0) {
+ return false;
+ }
+ double path_standard_deviation = get_path_standard_deviation(tc);
+ if (radius == 0 || path_standard_deviation == 0) {
+ return false;
+ }
+ double move_mean = get_move_mean(tc);
+ double move_standard_deviation = get_move_standard_deviation(tc);
+
+ {
+ std::vector<SPItem*> const items(set->items().begin(), set->items().end());
+
+ for(auto item : items){
+ g_assert(item != nullptr);
+ sp_object_ref(item);
+ }
+
+ for(auto item : items){
+ g_assert(item != nullptr);
+ if (sp_spray_recursive(desktop
+ , set
+ , item
+ , tc->single_path_output
+ , p, vector
+ , tc->mode
+ , radius
+ , population
+ , tc->scale
+ , tc->scale_variation
+ , reverse
+ , move_mean
+ , move_standard_deviation
+ , tc->ratio
+ , tc->tilt
+ , tc->rotation_variation
+ , tc->distrib
+ , tc->no_overlap
+ , tc->picker
+ , tc->pick_center
+ , tc->pick_inverse_value
+ , tc->pick_fill
+ , tc->pick_stroke
+ , tc->pick_no_overlap
+ , tc->over_no_transparent
+ , tc->over_transparent
+ , tc->offset
+ , tc->usepressurescale
+ , get_pressure(tc)
+ , tc->pick
+ , tc->do_trace
+ , tc->pick_to_size
+ , tc->pick_to_presence
+ , tc->pick_to_color
+ , tc->pick_to_opacity
+ , tc->invert_picked
+ , tc->gamma_picked
+ , tc->rand_picked)) {
+ did = true;
+ }
+ }
+
+ for(auto item : items){
+ g_assert(item != nullptr);
+ sp_object_unref(item);
+ }
+ }
+
+ return did;
+}
+
+static void sp_spray_update_area(SprayTool *tc)
+{
+ double radius = get_dilate_radius(tc);
+ Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) *
+ Geom::Rotate(tc->tilt) *
+ Geom::Translate(tc->getDesktop()->point()));
+
+ Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); // Unit circle centered at origin.
+ path *= sm;
+ tc->dilate_area->set_bpath(path);
+ tc->dilate_area->show();
+}
+
+static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift)
+{
+ // Select the button mode
+ auto tb = dynamic_cast<UI::Toolbar::SprayToolbar*>(tc->getDesktop()->get_toolbar_by_name("SprayToolbar"));
+
+ if(tb) {
+ tb->set_mode(mode);
+ } else {
+ std::cerr << "Could not access Spray toolbar" << std::endl;
+ }
+
+ // Need to set explicitly, because the prefs may not have changed by the previous
+ tc->mode = mode;
+ tc->update_cursor(with_shift);
+}
+
+bool SprayTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ dilate_area->show();
+ break;
+ case GDK_LEAVE_NOTIFY:
+ dilate_area->hide();
+ break;
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1) {
+ _desktop->getSelection()->restoreBackup();
+ if (Inkscape::have_viable_layer(_desktop, defaultMessageContext()) == false) {
+ return TRUE;
+ }
+ this->setCloneTilerPrefs();
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(_desktop->w2d(motion_w));
+ this->last_push = _desktop->dt2doc(motion_dt);
+
+ sp_spray_extinput(this, event);
+
+ set_high_motion_precision();
+ this->is_drawing = true;
+ this->is_dilating = true;
+ this->has_dilated = false;
+
+ object_set = *_desktop->getSelection();
+ if (mode == SPRAY_MODE_SINGLE_PATH) {
+ this->single_path_output = nullptr;
+ }
+
+ sp_spray_dilate(this, motion_w, this->last_push, Geom::Point(0,0), MOD__SHIFT(event));
+
+ this->has_dilated = true;
+ ret = TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY: {
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point motion_dt(_desktop->w2d(motion_w));
+ Geom::Point motion_doc(_desktop->dt2doc(motion_dt));
+ sp_spray_extinput(this, event);
+
+ // Draw the dilating cursor
+ double radius = get_dilate_radius(this);
+ Geom::Affine const sm (Geom::Scale(radius/(1-this->ratio), radius/(1+this->ratio)) *
+ Geom::Rotate(this->tilt) *
+ Geom::Translate(_desktop->w2d(motion_w)));
+
+ Geom::PathVector path = Geom::Path(Geom::Circle(0, 0, 1)); // Unit circle centered at origin.
+ path *= sm;
+ this->dilate_area->set_bpath(path);
+ this->dilate_area->show();
+
+ guint num = 0;
+ if (!_desktop->selection->isEmpty()) {
+ num = (guint)boost::distance(_desktop->selection->items());
+ }
+ if (num == 0) {
+ this->message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray."));
+ }
+
+ // Dilating:
+ if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
+ sp_spray_dilate(this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
+ //this->last_push = motion_doc;
+ this->has_dilated = true;
+
+ // It's slow, so prevent clogging up with events
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ return TRUE;
+ }
+ }
+ break;
+ /* Spray with the scroll */
+ case GDK_SCROLL: {
+ if (event->scroll.state & GDK_BUTTON1_MASK) {
+ double temp ;
+ temp = this->population;
+ this->population = 1.0;
+ _desktop->setToolboxAdjustmentValue("population", this->population * 100);
+ Geom::Point const scroll_w(event->button.x, event->button.y);
+ Geom::Point const scroll_dt = _desktop->point();;
+
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_UP:
+ case GDK_SCROLL_SMOOTH: {
+ if (Inkscape::have_viable_layer(_desktop, defaultMessageContext()) == false) {
+ return TRUE;
+ }
+ this->last_push = _desktop->dt2doc(scroll_dt);
+ sp_spray_extinput(this, event);
+ this->is_drawing = true;
+ this->is_dilating = true;
+ this->has_dilated = false;
+ if(this->is_dilating) {
+ sp_spray_dilate(this, scroll_w, _desktop->dt2doc(scroll_dt), Geom::Point(0, 0), false);
+ }
+ this->has_dilated = true;
+
+ this->population = temp;
+ _desktop->setToolboxAdjustmentValue("population", this->population * 100);
+
+ ret = TRUE;
+ }
+ break;
+ case GDK_SCROLL_RIGHT:
+ {} break;
+ case GDK_SCROLL_LEFT:
+ {} break;
+ }
+ }
+ break;
+ }
+
+ case GDK_BUTTON_RELEASE: {
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(_desktop->w2d(motion_w));
+
+ set_high_motion_precision(false);
+ this->is_drawing = false;
+
+ if (this->is_dilating && event->button.button == 1) {
+ if (!this->has_dilated) {
+ // If we did not rub, do a light tap
+ this->pressure = 0.03;
+ sp_spray_dilate(this, motion_w, _desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event));
+ }
+ this->is_dilating = false;
+ this->has_dilated = false;
+ switch (this->mode) {
+ case SPRAY_MODE_COPY:
+ DocumentUndo::done(_desktop->getDocument(), _("Spray with copies"), INKSCAPE_ICON("tool-spray"));
+ break;
+ case SPRAY_MODE_CLONE:
+ DocumentUndo::done(_desktop->getDocument(), _("Spray with clones"), INKSCAPE_ICON("tool-spray"));
+ break;
+ case SPRAY_MODE_SINGLE_PATH:
+ DocumentUndo::done(_desktop->getDocument(), _("Spray in single path"), INKSCAPE_ICON("tool-spray"));
+ break;
+ }
+ }
+ _desktop->getSelection()->clear();
+ object_set.clear();
+ break;
+ }
+
+ case GDK_KEY_PRESS:
+ switch (get_latin_keyval (&event->key)) {
+ case GDK_KEY_j:
+ case GDK_KEY_J:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_COPY, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_k:
+ case GDK_KEY_K:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_CLONE, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
+ case GDK_KEY_l:
+ case GDK_KEY_L:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+#endif
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->population += 0.01;
+ if (this->population > 1.0) {
+ this->population = 1.0;
+ }
+ _desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->population -= 0.01;
+ if (this->population < 0.0) {
+ this->population = 0.0;
+ }
+ _desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width += 0.01;
+ if (this->width > 1.0) {
+ this->width = 1.0;
+ }
+ // The same spinbutton is for alt+x
+ _desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width -= 0.01;
+ if (this->width < 0.01) {
+ this->width = 0.01;
+ }
+ _desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ this->width = 0.01;
+ _desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ this->width = 1.0;
+ _desktop->setToolboxAdjustmentValue("spray-width", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ _desktop->setToolboxFocusTo("spray-width");
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(true);
+ break;
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ break;
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE: {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch (get_latin_keyval(&event->key)) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(false);
+ break;
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
+ this->message_context->clear();
+ break;
+ default:
+ sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
+ break;
+ }
+ }
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+// if ((SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler) {
+// ret = (SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler(event_context, event);
+// }
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+