#define __SP_SPRAY_CONTEXT_C__

/*
 * 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)
 *
 * Copyright (C) 2009 authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include "config.h"

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glibmm/i18n.h>

#include <numeric>

#include "svg/svg.h"
#include "display/canvas-bpath.h"

#include <glib/gmem.h>
#include "macros.h"
#include "document.h"
#include "selection.h"
#include "desktop.h"
#include "desktop-events.h"
#include "desktop-handles.h"
#include "unistd.h"
#include "desktop-style.h"
#include "message-context.h"
#include "pixmaps/cursor-spray.xpm"
#include <boost/optional.hpp>
#include "libnr/nr-matrix-ops.h"
#include "libnr/nr-scale-translate-ops.h"
#include "xml/repr.h"
#include "context-fns.h"
#include "sp-item.h"
#include "inkscape.h"
#include "color.h"
#include "svg/svg-color.h"
#include "splivarot.h"
#include "sp-item-group.h"
#include "sp-shape.h"
#include "sp-path.h"
#include "path-chemistry.h"
#include "sp-gradient.h"
#include "sp-stop.h"
#include "sp-gradient-reference.h"
#include "sp-linear-gradient.h"
#include "sp-radial-gradient.h"
#include "gradient-chemistry.h"
#include "sp-text.h"
#include "sp-flowtext.h"
#include "display/canvas-bpath.h"
#include "display/canvas-arena.h"
#include "display/curve.h"
#include "livarot/Shape.h"
#include <2geom/isnan.h>
#include <2geom/transforms.h>
#include "preferences.h"
#include "style.h"
#include "box3d.h"
#include "sp-item-transform.h"
#include "filter-chemistry.h"
#include "sp-gaussian-blur-fns.h"
#include "sp-gaussian-blur.h"

#include "spray-context.h"
#include "ui/dialog/dialog-manager.h"
#include "helper/action.h"

#include <iostream>
using namespace std;


#define DDC_RED_RGBA 0xff0000ff

#define DYNA_MIN_WIDTH 1.0e-6

static void sp_spray_context_class_init(SPSprayContextClass *klass);
static void sp_spray_context_init(SPSprayContext *ddc);
static void sp_spray_context_dispose(GObject *object);

static void sp_spray_context_setup(SPEventContext *ec);
static void sp_spray_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
static gint sp_spray_context_root_handler(SPEventContext *ec, GdkEvent *event);

static SPEventContextClass *parent_class = 0;


/**
 * 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) );
}


GtkType sp_spray_context_get_type(void)
{
    static GType type = 0;
    if (!type) {
        GTypeInfo info = {
            sizeof(SPSprayContextClass),
            NULL, NULL,
            (GClassInitFunc) sp_spray_context_class_init,
            NULL, NULL,
            sizeof(SPSprayContext),
            4,
            (GInstanceInitFunc) sp_spray_context_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSprayContext", &info, (GTypeFlags)0);
    }
    return type;
}

static void sp_spray_context_class_init(SPSprayContextClass *klass)
{
    GObjectClass *object_class = (GObjectClass *) klass;
    SPEventContextClass *event_context_class = (SPEventContextClass *) klass;

    parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);

    object_class->dispose = sp_spray_context_dispose;

    event_context_class->setup = sp_spray_context_setup;
    event_context_class->set = sp_spray_context_set;
    event_context_class->root_handler = sp_spray_context_root_handler;
}

/* Method to rotate items */
void sp_spray_rotate_rel(Geom::Point c,SPDesktop */*desktop*/,SPItem *item, Geom::Rotate const &rotation)
{
    Geom::Point center = c;
    Geom::Translate const s(c);
    Geom::Matrix affine = Geom::Matrix(s).inverse() * Geom::Matrix(rotation) * Geom::Matrix(s);
    // Rotate item.
    sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * (Geom::Matrix)affine);
    // Use each item's own transform writer, consistent with sp_selection_apply_affine()
    sp_item_write_transform(item, SP_OBJECT_REPR(item), 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 */
void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale  const &scale)
{
    Geom::Translate const s(c);
    sp_item_set_i2d_affine(item, sp_item_i2d_affine(item) * s.inverse() * scale * s  );
    sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform);
}

static void sp_spray_context_init(SPSprayContext *tc)
{
    SPEventContext *event_context = SP_EVENT_CONTEXT(tc);

    event_context->cursor_shape = cursor_spray_xpm;
    event_context->hot_x = 4;
    event_context->hot_y = 4;

    /* attributes */
    tc->dragging = FALSE;
    tc->distrib = 1;
    tc->width = 0.2;
    tc->force = 0.2;
    tc->ratio = 0;
    tc->tilt=0;
    tc->mean = 0.2;
    tc->rotation_variation=0;
    tc->standard_deviation=0.2;
    tc->scale=1;
    tc->scale_variation = 1;
    tc->pressure = TC_DEFAULT_PRESSURE;

    tc->is_dilating = false;
    tc->has_dilated = false;

    tc->do_h = true;
    tc->do_s = true;
    tc->do_l = true;
    tc->do_o = false;

    new (&tc->style_set_connection) sigc::connection();
}

static void sp_spray_context_dispose(GObject *object)
{
    SPSprayContext *tc = SP_SPRAY_CONTEXT(object);

    tc->style_set_connection.disconnect();
    tc->style_set_connection.~connection();

    if (tc->dilate_area) {
        gtk_object_destroy(GTK_OBJECT(tc->dilate_area));
        tc->dilate_area = NULL;
    }

    if (tc->_message_context) {
        delete tc->_message_context;
    }

    G_OBJECT_CLASS(parent_class)->dispose(object);
}

bool is_transform_modes(gint mode)
{
    return (mode == SPRAY_MODE_COPY ||
            mode == SPRAY_MODE_CLONE ||
            mode == SPRAY_MODE_SINGLE_PATH ||
            mode == SPRAY_OPTION);
}

void sp_spray_update_cursor(SPSprayContext *tc, bool /*with_shift*/)
{
   SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
   SPDesktop *desktop = event_context->desktop;

    guint num = 0;
    gchar *sel_message = NULL;
    if (!desktop->selection->isEmpty()) {
        num = g_slist_length((GSList *) desktop->selection->itemList());
        sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
    } else {
        sel_message = g_strdup_printf(_("<b>Nothing</b> selected"));
    }


   switch (tc->mode) {
       case SPRAY_MODE_COPY:
           tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray <b>copies</b> of the initial selection"), sel_message);
           break;
       case SPRAY_MODE_CLONE:
           tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray <b>clones</b> of the initial selection"), sel_message);
           break;
       case SPRAY_MODE_SINGLE_PATH:
           tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or scroll to spray in a <b>single path</b> of the initial selection"), sel_message);
           break;
       default:
           break;
   }
   sp_event_context_update_cursor(event_context);
   g_free(sel_message);
}

static void sp_spray_context_setup(SPEventContext *ec)
{
    SPSprayContext *tc = SP_SPRAY_CONTEXT(ec);

    if (((SPEventContextClass *) parent_class)->setup)
        ((SPEventContextClass *) parent_class)->setup(ec);

    {
        /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */
        SPCurve *c = new SPCurve();
        const double C1 = 0.552;
        c->moveto(-1,0);
        c->curveto(-1, C1, -C1, 1, 0, 1 );
        c->curveto(C1, 1, 1, C1, 1, 0 );
        c->curveto(1, -C1, C1, -1, 0, -1 );
        c->curveto(-C1, -1, -1, -C1, -1, 0 );
        c->closepath();
        tc->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(ec->desktop), c);
        c->unref();
        sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(tc->dilate_area), 0x00000000,(SPWindRule)0);
        sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(tc->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
        sp_canvas_item_hide(tc->dilate_area);
    }

    tc->is_drawing = false;

    tc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());

    sp_event_context_read(ec, "distrib");
    sp_event_context_read(ec, "width");
    sp_event_context_read(ec, "ratio");
    sp_event_context_read(ec, "tilt");
    sp_event_context_read(ec, "rotation_variation");
    sp_event_context_read(ec, "scale_variation");
    sp_event_context_read(ec, "mode");
    sp_event_context_read(ec, "population");
    sp_event_context_read(ec, "force");
    sp_event_context_read(ec, "mean");
    sp_event_context_read(ec, "standard_deviation");
    sp_event_context_read(ec, "usepressure");
    sp_event_context_read(ec, "Scale");
    sp_event_context_read(ec, "doh");
    sp_event_context_read(ec, "dol");
    sp_event_context_read(ec, "dos");
    sp_event_context_read(ec, "doo");

    ;

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    if (prefs->getBool("/tools/spray/selcue")) {
        ec->enableSelectionCue();
    }

    if (prefs->getBool("/tools/spray/gradientdrag")) {
        ec->enableGrDrag();
    }
}

static void sp_spray_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
{
    SPSprayContext *tc = SP_SPRAY_CONTEXT(ec);
    Glib::ustring path = val->getEntryName();

    if (path == "width") {
        tc->width = 0.01 * CLAMP(val->getInt(10), 1, 100);
    } else if (path == "mode") {
        tc->mode = val->getInt();
        sp_spray_update_cursor(tc, false);
    } else if (path == "distribution") {
        tc->distrib = val->getInt(1);
    } else if (path == "population") {
        tc->population = 0.01 * CLAMP(val->getInt(10), 1, 100);
     } else if (path == "tilt") {
        tc->tilt = CLAMP(val->getDouble(0.1), 0, 1000.0);
     } else if (path == "ratio") {
        tc->ratio = CLAMP(val->getDouble(), 0.0, 0.9);
    } else if (path == "force") {
        tc->force = CLAMP(val->getDouble(1.0), 0, 1.0);
    } else if (path == "rotation_variation") {
        tc->rotation_variation = CLAMP(val->getDouble(0.0), 0, 100.0);
    } else if (path == "scale_variation") {
        tc->scale_variation = CLAMP(val->getDouble(1.0), 0, 100.0);
    } else if (path == "mean") {
        tc->mean = 0.01 * CLAMP(val->getInt(10), 1, 100);
    } else if (path == "standard_deviation") {
        tc->standard_deviation = 0.01 * CLAMP(val->getInt(10), 1, 100);
    } else if (path == "usepressure") {
        tc->usepressure = val->getBool();
    } else if (path == "doh") {
        tc->do_h = val->getBool();
    } else if (path == "dos") {
        tc->do_s = val->getBool();
    } else if (path == "dol") {
        tc->do_l = val->getBool();
    } else if (path == "doo") {
        tc->do_o = val->getBool();
    }
}

static void sp_spray_extinput(SPSprayContext *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;
}

double get_dilate_radius(SPSprayContext *tc)
{

    return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom();


}

double get_path_force(SPSprayContext *tc)
{
    double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE)
        /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom());
    if (force > 3) {
        force += 4 * (force - 3);
    }
    return force * tc->force;
}

double get_path_mean(SPSprayContext *tc)
{
    return tc->mean;
}

double get_path_standard_deviation(SPSprayContext *tc)
{
    return tc->standard_deviation;
}

double get_move_force(SPSprayContext *tc)
{
    double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE);
    return force * tc->force;
}

double get_move_mean(SPSprayContext *tc)
{
    return tc->mean;
}

double get_move_standard_deviation(SPSprayContext *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 : 

 */
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);

}





bool sp_spray_recursive(SPDesktop *desktop,
                               Inkscape::Selection *selection,
                               SPItem *item,
                               Geom::Point p,
                               Geom::Point /*vector*/,
                               gint mode,
                               double radius,
                               double /*force*/,
                               double population,
                               double &scale,
                               double scale_variation,
                               bool /*reverse*/,
                               double mean,
                               double standard_deviation,
                               double ratio,
                               double tilt,
                               double rotation_variation,
                               gint _distrib )
{
    bool did = false;
    
    if (SP_IS_BOX3D(item) ) {
        // convert 3D boxes to ordinary groups before spraying their shapes
        item = SP_ITEM(box3d_convert_to_group(SP_BOX3D(item)));
        selection->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 );
    double dr; double dp;
    random_position( dr, dp, mean, standard_deviation, _distrib );
    dr=dr*radius;

    if (mode == SPRAY_MODE_COPY) {
        Geom::OptRect a = item->getBounds(sp_item_i2doc_affine(item));
        if (a) {
            SPItem *item_copied;
            if(_fid<=population)
            {
                // duplicate
                SPDocument *doc = SP_OBJECT_DOCUMENT(item);
                Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc);
                Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(item);
                Inkscape::XML::Node *parent = old_repr->parent();
                Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
                parent->appendChild(copy);

                SPObject *new_obj = doc->getObjectByRepr(copy);
                item_copied = (SPItem *) new_obj;   //convertion object->item
                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));
                //Move the cursor p
                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());
                sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));
                did = true;
            }
        }
    } else if (mode == SPRAY_MODE_SINGLE_PATH) {

        SPItem *father;         //initial Object
        SPItem *item_copied;    //Projected Object
        SPItem *unionResult;    //previous union
        SPItem *son;            //father copy

        int i=1;
        for (GSList *items = g_slist_copy((GSList *) selection->itemList());
                items != NULL;
                items = items->next) {

            SPItem *item1 = (SPItem *) items->data;
            if (i==1) {
                father=item1;
            }
            if (i==2) {
                unionResult=item1;
            }
            i++;
        }
        SPDocument *doc = SP_OBJECT_DOCUMENT(father);
        Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc);
        Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(father);
        Inkscape::XML::Node *parent = old_repr->parent();

        Geom::OptRect a = father->getBounds(sp_item_i2doc_affine(father));
        if (a) {
            if (i==2) {
                Inkscape::XML::Node *copy1 = old_repr->duplicate(xml_doc);
                parent->appendChild(copy1);
                SPObject *new_obj1 = doc->getObjectByRepr(copy1);
                son = (SPItem *) new_obj1;   // conversion object->item
                unionResult=son;
                Inkscape::GC::release(copy1);
               }

            if (_fid<=population) { // Rules the population of objects sprayed
                // duplicates the father
                Inkscape::XML::Node *copy2 = old_repr->duplicate(xml_doc);
                parent->appendChild(copy2);
                SPObject *new_obj2 = doc->getObjectByRepr(copy2);
                item_copied = (SPItem *) new_obj2;

                // 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=father->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));
                sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));

                // union and duplication
                selection->clear();
                selection->add(item_copied);
                selection->add(unionResult);
                sp_selected_path_union_skip_undo(selection->desktop());
                selection->add(father);
                Inkscape::GC::release(copy2);
                did = true;
            }
        }
    } else if (mode == SPRAY_MODE_CLONE) {
        Geom::OptRect a = item->getBounds(sp_item_i2doc_affine(item));
        if (a) {
            if(_fid<=population) {
                SPItem *item_copied;
                SPDocument *doc = SP_OBJECT_DOCUMENT(item);
                Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc);
                Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(item);
                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 father's sons
                parent->appendChild(clone);
                // Generates the link between father and son attributes
                clone->setAttribute("xlink:href", g_strdup_printf("#%s", old_repr->attribute("id")), false); 

                SPObject *clone_object = doc->getObjectByRepr(clone);
                // conversion object->item
                item_copied = (SPItem *) clone_object;
                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));
                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());
                sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));

                Inkscape::GC::release(clone);

                did = true;
            }
        }
    }

    return did;
}

bool sp_spray_dilate(SPSprayContext *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse)
{
    Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop);
    SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop;


    if (selection->isEmpty()) {
        return false;
    }

    bool did = false;
    double radius = get_dilate_radius(tc);



    bool do_fill = false, do_stroke = false, do_opacity = false;
    guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/spray", true, &do_fill);
    guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/spray", false, &do_stroke);
    double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/spray", &do_opacity);
    if (reverse) {
        // RGB inversion
        fill_goal = SP_RGBA32_U_COMPOSE(
            (255 - SP_RGBA32_R_U(fill_goal)),
            (255 - SP_RGBA32_G_U(fill_goal)),
            (255 - SP_RGBA32_B_U(fill_goal)),
            (255 - SP_RGBA32_A_U(fill_goal)));
        stroke_goal = SP_RGBA32_U_COMPOSE(
            (255 - SP_RGBA32_R_U(stroke_goal)),
            (255 - SP_RGBA32_G_U(stroke_goal)),
            (255 - SP_RGBA32_B_U(stroke_goal)),
            (255 - SP_RGBA32_A_U(stroke_goal)));
        opacity_goal = 1 - opacity_goal;
    }

    double path_force = get_path_force(tc);
    if (radius == 0 || path_force == 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_force = get_move_force(tc);
    double move_mean = get_move_mean(tc);
    double move_standard_deviation = get_move_standard_deviation(tc);


    for (GSList *items = g_slist_copy((GSList *) selection->itemList());
         items != NULL;
         items = items->next) {

        SPItem *item = (SPItem *) items->data;

        if (is_transform_modes(tc->mode)) {
            if (sp_spray_recursive (desktop,selection, item, p, vector, tc->mode, radius, move_force, tc->population,tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation,tc->ratio,tc->tilt, tc->rotation_variation, tc->distrib))
                did = true;
        } else {
            if (sp_spray_recursive (desktop,selection, item, p, vector, tc->mode, radius, path_force, tc->population,tc->scale, tc->scale_variation, reverse, path_mean, path_standard_deviation,tc->ratio,tc->tilt, tc->rotation_variation, tc->distrib))
                did = true;
        }
    }

    return did;
}

void sp_spray_update_area(SPSprayContext *tc)
{
        double radius = get_dilate_radius(tc);
        Geom::Matrix const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) );
        sp_canvas_item_affine_absolute(tc->dilate_area,   (sm* Geom::Rotate(tc->tilt))* Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point()));
        sp_canvas_item_show(tc->dilate_area);
}

void sp_spray_switch_mode(SPSprayContext *tc, gint mode, bool with_shift)
{
    // select the button mode
    SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("spray_tool_mode", mode); 
    // need to set explicitly, because the prefs may not have changed by the previous
    tc->mode = mode;
    sp_spray_update_cursor (tc, with_shift);
}

void sp_spray_switch_mode_temporarily(SPSprayContext *tc, gint mode, bool with_shift)
{
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
   // Juggling about so that prefs have the old value but tc->mode and the button show new mode:
   gint now_mode = prefs->getInt("/tools/spray/mode", 0);
   SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("spray_tool_mode", mode);
   // button has changed prefs, restore
   prefs->setInt("/tools/spray/mode", now_mode);
   // changing prefs changed tc->mode, restore back :)
   tc->mode = mode;
   sp_spray_update_cursor (tc, with_shift);
}

gint sp_spray_context_root_handler(SPEventContext *event_context,
                                  GdkEvent *event)
{
    SPSprayContext *tc = SP_SPRAY_CONTEXT(event_context);
    SPDesktop *desktop = event_context->desktop;

    gint ret = FALSE;

    switch (event->type) {
        case GDK_ENTER_NOTIFY:
            sp_canvas_item_show(tc->dilate_area);
            break;
        case GDK_LEAVE_NOTIFY:
            sp_canvas_item_hide(tc->dilate_area);
            break;
        case GDK_BUTTON_PRESS:
            if (event->button.button == 1 && !event_context->space_panning) {

                if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false) {
                    return TRUE;
                }

                Geom::Point const motion_w(event->button.x,
                                         event->button.y);
                Geom::Point const motion_dt(desktop->w2d(motion_w));
                tc->last_push = desktop->dt2doc(motion_dt);

                sp_spray_extinput(tc, event);

                sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
                tc->is_drawing = true;
                tc->is_dilating = true;
                                tc->has_dilated = false;



                if(tc->is_dilating && event->button.button == 1 && !event_context->space_panning)

                                sp_spray_dilate (tc, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT);



                                                          tc->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(tc, event);

            // draw the dilating cursor
                double radius = get_dilate_radius(tc);
                Geom::Matrix const sm (Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) );
                sp_canvas_item_affine_absolute(tc->dilate_area, (sm*Geom::Rotate(tc->tilt))*Geom::Translate(desktop->w2d(motion_w)));
                sp_canvas_item_show(tc->dilate_area);

                guint num = 0;
                if (!desktop->selection->isEmpty()) {
                    num = g_slist_length((GSList *) desktop->selection->itemList());
                }
                if (num == 0) {
                    tc->_message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray."));
                }

            // dilating:
            if (tc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
                sp_spray_dilate (tc, motion_w, motion_doc, motion_doc - tc->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
                //tc->last_push = motion_doc;
                tc->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=tc->population;
                  tc->population=1.0;
                  desktop->setToolboxAdjustmentValue ("population", tc->population * 100);
                  Geom::Point const scroll_w(event->button.x,event->button.y);
                  Geom::Point const scroll_dt = desktop->point();;
                  Geom::Point motion_doc(desktop->dt2doc(scroll_dt));
                       switch (event->scroll.direction)
                       {
                         case GDK_SCROLL_UP:
                           {
                                   if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false)
                                   {
                                    return TRUE;
                                   }
                                   tc->last_push = desktop->dt2doc(scroll_dt);
                                   sp_spray_extinput(tc, event);
                                   sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
                                   tc->is_drawing = true;
                                   tc->is_dilating = true;
                                   tc->has_dilated = false;
                                   if(tc->is_dilating && !event_context->space_panning)

                                                        sp_spray_dilate (tc, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0),false);



                                   tc->has_dilated=true;
                                   tc->population=temp;

                                   desktop->setToolboxAdjustmentValue ("population", tc->population * 100);

                                   ret = TRUE;
                           }
                               break;
                           case GDK_SCROLL_DOWN:
                           {
                                   if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false)
                                                                   {
                                                                    return TRUE;
                                                                   }
                                                                   tc->last_push = desktop->dt2doc(scroll_dt);
                                                                   sp_spray_extinput(tc, event);
                                                                   sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
                                                                   tc->is_drawing = true;
                                                                   tc->is_dilating = true;
                                                                   tc->has_dilated = false;
                                                                   if(tc->is_dilating && !event_context->space_panning)
                                                                                        sp_spray_dilate (tc, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false);

                                                                   tc->has_dilated=true;

                                                                   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));

        sp_canvas_end_forced_full_redraws(desktop->canvas);
        tc->is_drawing = false;

        if (tc->is_dilating && event->button.button == 1 && !event_context->space_panning) {
            if (!tc->has_dilated) {
                // if we did not rub, do a light tap
                tc->pressure = 0.03;
                sp_spray_dilate (tc, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT);
            }
            tc->is_dilating = false;
            tc->has_dilated = false;
            switch (tc->mode) {
                case SPRAY_MODE_COPY:
                    sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
                                     SP_VERB_CONTEXT_SPRAY, _("Spray with copies"));
                    break;
                case SPRAY_MODE_CLONE:
                    sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
                                     SP_VERB_CONTEXT_SPRAY, _("Spray with clones"));
                    break;
                case SPRAY_MODE_SINGLE_PATH:
                    sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
                                     SP_VERB_CONTEXT_SPRAY, _("Spray in single path"));
                    break;
            }
        }
        break;
    }

    case GDK_KEY_PRESS:
        switch (get_group0_keyval (&event->key)) {
case GDK_j:  if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_COPY, MOD__SHIFT);
                ret = TRUE;
            }
case GDK_J: if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_COPY, MOD__SHIFT);
                ret = TRUE;
            }

break;
        case GDK_m:
        case GDK_M:
        case GDK_0:

            break;
        case GDK_i:
        case GDK_I:
        case GDK_k:    if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT);
                ret = TRUE;
            }
        case GDK_K:if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT);
                ret = TRUE;
            }
break;

        case GDK_l: if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_CLONE, MOD__SHIFT);
                ret = TRUE;
            }

        case GDK_L:
 if (MOD__SHIFT_ONLY) {
                sp_spray_switch_mode(tc, SPRAY_MODE_CLONE, MOD__SHIFT);
                ret = TRUE;
            }
            break;
        case GDK_Up:
        case GDK_KP_Up:
            if (!MOD__CTRL_ONLY) {
                tc->scale += 0.05;

                //desktop->setToolboxAdjustmentValue ("spray-force", tc->force * 100);
                ret = TRUE;
            }
            break;
        case GDK_Down:
        case GDK_KP_Down:
            if (!MOD__CTRL_ONLY) {

                tc->scale -= 0.05;
                if (tc->scale < 0.0)
                    tc->scale = 0.0;
                //desktop->setToolboxAdjustmentValue ("spray-force", tc->force * 100);

                ret = TRUE;

            }
            break;
        case GDK_Right:
        case GDK_KP_Right:
            if (!MOD__CTRL_ONLY) {
                tc->width += 0.01;
                if (tc->width > 1.0)
                    tc->width = 1.0;
                desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100); // the same spinbutton is for alt+x
                sp_spray_update_area(tc);
                ret = TRUE;
            }
            break;
        case GDK_Left:
        case GDK_KP_Left:
            if (!MOD__CTRL_ONLY) {
                tc->width -= 0.01;
                if (tc->width < 0.01)
                    tc->width = 0.01;
                desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100);
                sp_spray_update_area(tc);
                ret = TRUE;
            }
            break;
        case GDK_Home:
        case GDK_KP_Home:
            tc->width = 0.01;
            desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100);
            sp_spray_update_area(tc);
            ret = TRUE;
            break;
        case GDK_End:
        case GDK_KP_End:
            tc->width = 1.0;
            desktop->setToolboxAdjustmentValue ("altx-spray", tc->width * 100);
            sp_spray_update_area(tc);
            ret = TRUE;
            break;
        case GDK_x:
        case GDK_X:
            if (MOD__ALT_ONLY) {
                desktop->setToolboxFocusTo ("altx-spray");
                ret = TRUE;
            }
            break;

        case GDK_Shift_L:
        case GDK_Shift_R:
            sp_spray_update_cursor(tc, true);
            break;
/*Set the scale to 1*/
        case GDK_Control_L:
                tc->scale=1;
        default:
            break;
        }
        break;

    case GDK_KEY_RELEASE: {
        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        switch (get_group0_keyval(&event->key)) {
            case GDK_Shift_L:
            case GDK_Shift_R:
                sp_spray_update_cursor(tc, false);
                break;
            case GDK_Control_L:
            case GDK_Control_R:
                sp_spray_switch_mode (tc, prefs->getInt("/tools/spray/mode"), MOD__SHIFT);
                tc->_message_context->clear();
                break;
            default:
                sp_spray_switch_mode (tc, prefs->getInt("/tools/spray/mode"), MOD__SHIFT);
                break;
        }
    }

    default:
        break;
    }

    if (!ret) {
        if (((SPEventContextClass *) parent_class)->root_handler) {
            ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, 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:encoding=utf-8:textwidth=99 :

