diff options
Diffstat (limited to 'src/ui/tools/spray-tool.cpp')
-rw-r--r-- | src/ui/tools/spray-tool.cpp | 1528 |
1 files changed, 1528 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..c6089b3 --- /dev/null +++ b/src/ui/tools/spray-tool.cpp @@ -0,0 +1,1528 @@ +// 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" +#include "ui/widget/canvas.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) + , 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 = make_canvasitem<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(); +} + +void SprayTool::update_cursor(bool /*with_shift*/) { + guint num = 0; + gchar *sel_message = nullptr; + + if (!_desktop->getSelection()->isEmpty()) { + num = (guint)boost::distance(_desktop->getSelection()->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(); + + // Get average color. + double R, G, B, A; + drawing->averageColor(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. + if (auto box = cast<SPBox3D>(item)) { + desktop->getSelection()->remove(item); + set->remove(item); + item = box->convert_to_group(); + set->add(item); + desktop->getSelection()->add(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 = 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); + auto item_copied = 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 (is<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 = 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->getSelection()->isEmpty()) { + num = (guint)boost::distance(_desktop->getSelection()->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 : + |