Code

df10ca7a1605a391bdc332ebf021c920314e2753
[inkscape.git] / src / tweak-context.cpp
1 #define __SP_TWEAK_CONTEXT_C__
3 /*
4  * tweaking paths without node editing
5  *
6  * Authors:
7  *   bulia byak
8  *
9  * Copyright (C) 2007 authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #include "config.h"
16 #include <gtk/gtk.h>
17 #include <gdk/gdkkeysyms.h>
18 #include <glibmm/i18n.h>
20 #include <numeric>
22 #include "svg/svg.h"
23 #include "display/canvas-bpath.h"
24 #include "display/bezier-utils.h"
26 #include <glib/gmem.h>
27 #include "macros.h"
28 #include "document.h"
29 #include "selection.h"
30 #include "desktop.h"
31 #include "desktop-events.h"
32 #include "desktop-handles.h"
33 #include "desktop-affine.h"
34 #include "desktop-style.h"
35 #include "message-context.h"
36 #include "pixmaps/cursor-tweak-move.xpm"
37 #include "pixmaps/cursor-thin.xpm"
38 #include "pixmaps/cursor-thicken.xpm"
39 #include "pixmaps/cursor-attract.xpm"
40 #include "pixmaps/cursor-repel.xpm"
41 #include "pixmaps/cursor-push.xpm"
42 #include "pixmaps/cursor-roughen.xpm"
43 #include "pixmaps/cursor-color.xpm"
44 #include <boost/optional.hpp>
45 #include "libnr/nr-matrix-ops.h"
46 #include "libnr/nr-scale-translate-ops.h"
47 #include "xml/repr.h"
48 #include "context-fns.h"
49 #include "sp-item.h"
50 #include "inkscape.h"
51 #include "color.h"
52 #include "svg/svg-color.h"
53 #include "splivarot.h"
54 #include "sp-item-group.h"
55 #include "sp-shape.h"
56 #include "sp-path.h"
57 #include "path-chemistry.h"
58 #include "sp-gradient.h"
59 #include "sp-stop.h"
60 #include "sp-stop-fns.h"
61 #include "sp-gradient-reference.h"
62 #include "sp-linear-gradient.h"
63 #include "sp-radial-gradient.h"
64 #include "gradient-chemistry.h"
65 #include "sp-text.h"
66 #include "sp-flowtext.h"
67 #include "display/canvas-bpath.h"
68 #include "display/canvas-arena.h"
69 #include "display/curve.h"
70 #include "livarot/Shape.h"
71 #include <2geom/isnan.h>
72 #include <2geom/transforms.h>
73 #include "prefs-utils.h"
74 #include "style.h"
75 #include "box3d.h"
76 #include "sp-item-transform.h"
77 #include "filter-chemistry.h"
78 #include "sp-gaussian-blur-fns.h"
79 #include "sp-gaussian-blur.h"
81 #include "tweak-context.h"
83 #define DDC_RED_RGBA 0xff0000ff
85 #define DYNA_MIN_WIDTH 1.0e-6
87 static void sp_tweak_context_class_init(SPTweakContextClass *klass);
88 static void sp_tweak_context_init(SPTweakContext *ddc);
89 static void sp_tweak_context_dispose(GObject *object);
91 static void sp_tweak_context_setup(SPEventContext *ec);
92 static void sp_tweak_context_set(SPEventContext *ec, gchar const *key, gchar const *val);
93 static gint sp_tweak_context_root_handler(SPEventContext *ec, GdkEvent *event);
95 static SPEventContextClass *parent_class;
97 GtkType
98 sp_tweak_context_get_type(void)
99 {
100     static GType type = 0;
101     if (!type) {
102         GTypeInfo info = {
103             sizeof(SPTweakContextClass),
104             NULL, NULL,
105             (GClassInitFunc) sp_tweak_context_class_init,
106             NULL, NULL,
107             sizeof(SPTweakContext),
108             4,
109             (GInstanceInitFunc) sp_tweak_context_init,
110             NULL,   /* value_table */
111         };
112         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTweakContext", &info, (GTypeFlags)0);
113     }
114     return type;
117 static void
118 sp_tweak_context_class_init(SPTweakContextClass *klass)
120     GObjectClass *object_class = (GObjectClass *) klass;
121     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
123     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
125     object_class->dispose = sp_tweak_context_dispose;
127     event_context_class->setup = sp_tweak_context_setup;
128     event_context_class->set = sp_tweak_context_set;
129     event_context_class->root_handler = sp_tweak_context_root_handler;
132 static void
133 sp_tweak_context_init(SPTweakContext *tc)
135     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
137     event_context->cursor_shape = cursor_push_xpm;
138     event_context->hot_x = 4;
139     event_context->hot_y = 4;
141     /* attributes */
142     tc->dragging = FALSE;
144     tc->width = 0.2;
145     tc->force = 0.2;
146     tc->pressure = TC_DEFAULT_PRESSURE;
148     tc->is_dilating = false;
149     tc->has_dilated = false;
151     tc->do_h = true;
152     tc->do_s = true;
153     tc->do_l = true;
154     tc->do_o = false;
156     new (&tc->style_set_connection) sigc::connection();
159 static void
160 sp_tweak_context_dispose(GObject *object)
162     SPTweakContext *tc = SP_TWEAK_CONTEXT(object);
164     tc->style_set_connection.disconnect();
165     tc->style_set_connection.~connection();
167     if (tc->dilate_area) {
168         gtk_object_destroy(GTK_OBJECT(tc->dilate_area));
169         tc->dilate_area = NULL;
170     }
172     if (tc->_message_context) {
173         delete tc->_message_context;
174     }
176     G_OBJECT_CLASS(parent_class)->dispose(object);
179 bool is_transform_mode (gint mode)
181     return (mode == TWEAK_MODE_MOVE || 
182             mode == TWEAK_MODE_MOVE_IN_OUT || 
183             mode == TWEAK_MODE_MOVE_JITTER || 
184             mode == TWEAK_MODE_SCALE || 
185             mode == TWEAK_MODE_ROTATE || 
186             mode == TWEAK_MODE_MORELESS);
189 bool is_color_mode (gint mode)
191     return (mode == TWEAK_MODE_COLORPAINT || mode == TWEAK_MODE_COLORJITTER || mode == TWEAK_MODE_BLUR);
194 void
195 sp_tweak_update_cursor (SPTweakContext *tc, bool with_shift)
197    SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
198    SPDesktop *desktop = event_context->desktop;
200                 guint num = 0;
201                 gchar *sel_message = NULL;
202                 if (!desktop->selection->isEmpty()) {
203                     num = g_slist_length((GSList *) desktop->selection->itemList());
204                     sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
205                 } else {
206                     sel_message = g_strdup_printf(_("<b>Nothing</b> selected"));
207                 }
210    switch (tc->mode) {
211        case TWEAK_MODE_MOVE:
212            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>move</b>."), sel_message);
213                            break;
214            event_context->cursor_shape = cursor_tweak_move_xpm;
215            break;
216        case TWEAK_MODE_MOVE_IN_OUT:
217            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move in</b>; with Shift to <b>move out</b>."), sel_message);
218            break;
219        case TWEAK_MODE_MOVE_JITTER:
220            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move randomly</b>."), sel_message);
221            break;
222        case TWEAK_MODE_SCALE:
223            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>scale down</b>; with Shift to <b>scale up</b>."), sel_message);
224            break;
225        case TWEAK_MODE_ROTATE:
226            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>rotate clockwise</b>; with Shift, <b>counterclockwise</b>."), sel_message);
227            break;
228        case TWEAK_MODE_MORELESS:
229            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>duplicate</b>; with Shift, <b>delete</b>."), sel_message);
230            break;
231        case TWEAK_MODE_PUSH:
232            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>push paths</b>."), sel_message);
233            event_context->cursor_shape = cursor_push_xpm;
234            break;
235        case TWEAK_MODE_SHRINK_GROW:
236            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>inset paths</b>; with Shift to <b>outset</b>."), sel_message);
237            if (with_shift) {
238                event_context->cursor_shape = cursor_thicken_xpm;
239            } else {
240                event_context->cursor_shape = cursor_thin_xpm;
241            }
242            break;
243        case TWEAK_MODE_ATTRACT_REPEL:
244            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>attract paths</b>; with Shift to <b>repel</b>."), sel_message);
245            if (with_shift) {
246                event_context->cursor_shape = cursor_repel_xpm;
247            } else {
248                event_context->cursor_shape = cursor_attract_xpm;
249            }
250            break;
251        case TWEAK_MODE_ROUGHEN:
252            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>roughen paths</b>."), sel_message);
253            event_context->cursor_shape = cursor_roughen_xpm;
254            break;
255        case TWEAK_MODE_COLORPAINT:
256            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>paint objects</b> with color."), sel_message);
257            break;
258        case TWEAK_MODE_COLORJITTER:
259            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>randomize colors</b>."), sel_message);
260            event_context->cursor_shape = cursor_color_xpm;
261            break;
262        case TWEAK_MODE_BLUR:
263            tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>increase blur</b>; with Shift to <b>decrease</b>."), sel_message);
264            event_context->cursor_shape = cursor_color_xpm;
265            break;
266    }
267    sp_event_context_update_cursor(event_context);
268    g_free(sel_message);
271 static bool
272 sp_tweak_context_style_set(SPCSSAttr const *css, SPTweakContext *tc)
274     if (tc->mode == TWEAK_MODE_COLORPAINT) { // intercept color setting only in this mode
275         // we cannot store properties with uris
276         css = sp_css_attr_unset_uris ((SPCSSAttr *) css);
278         sp_repr_css_change (inkscape_get_repr (INKSCAPE, "tools.tweak"), (SPCSSAttr *) css, "style");
280         return true;
281     }
282     return false;
286 static void
287 sp_tweak_context_setup(SPEventContext *ec)
289     SPTweakContext *tc = SP_TWEAK_CONTEXT(ec);
291     if (((SPEventContextClass *) parent_class)->setup)
292         ((SPEventContextClass *) parent_class)->setup(ec);
294     {
295         /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */
296         SPCurve *c = new SPCurve();
297         const double C1 = 0.552;
298         c->moveto(-1,0);
299         c->curveto(-1, C1, -C1, 1, 0, 1 );
300         c->curveto(C1, 1, 1, C1, 1, 0 );
301         c->curveto(1, -C1, C1, -1, 0, -1 );
302         c->curveto(-C1, -1, -1, -C1, -1, 0 );
303         c->closepath();
304         tc->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(ec->desktop), c);
305         c->unref();
306         sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(tc->dilate_area), 0x00000000,(SPWindRule)0);
307         sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(tc->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
308         sp_canvas_item_hide(tc->dilate_area);
309     }
311     tc->is_drawing = false;
313     tc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
315     sp_event_context_read(ec, "width");
316     sp_event_context_read(ec, "mode");
317     sp_event_context_read(ec, "fidelity");
318     sp_event_context_read(ec, "force");
319     sp_event_context_read(ec, "usepressure");
320     sp_event_context_read(ec, "doh");
321     sp_event_context_read(ec, "dol");
322     sp_event_context_read(ec, "dos");
323     sp_event_context_read(ec, "doo");
325     tc->style_set_connection = ec->desktop->connectSetStyle( // catch style-setting signal in this tool
326         sigc::bind(sigc::ptr_fun(&sp_tweak_context_style_set), tc)
327     );
329     if (prefs_get_int_attribute("tools.tweak", "selcue", 0) != 0) {
330         ec->enableSelectionCue();
331     }
333     if (prefs_get_int_attribute("tools.tweak", "gradientdrag", 0) != 0) {
334         ec->enableGrDrag();
335     }
338 static void
339 sp_tweak_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
341     SPTweakContext *tc = SP_TWEAK_CONTEXT(ec);
343     if (!strcmp(key, "width")) {
344         double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
345         tc->width = CLAMP(dval, -1000.0, 1000.0);
346     } else if (!strcmp(key, "mode")) {
347         gint64 const dval = ( val ? g_ascii_strtoll (val, NULL, 10) : 0 );
348         tc->mode = dval;
349         sp_tweak_update_cursor(tc, false);
350     } else if (!strcmp(key, "fidelity")) {
351         double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
352         tc->fidelity = CLAMP(dval, 0.0, 1.0);
353     } else if (!strcmp(key, "force")) {
354         double const dval = ( val ? g_ascii_strtod (val, NULL) : 1.0 );
355         tc->force = CLAMP(dval, 0, 1.0);
356     } else if (!strcmp(key, "usepressure")) {
357         tc->usepressure = (val && strcmp(val, "0"));
358     } else if (!strcmp(key, "doh")) {
359         tc->do_h = (val && strcmp(val, "0"));
360     } else if (!strcmp(key, "dos")) {
361         tc->do_s = (val && strcmp(val, "0"));
362     } else if (!strcmp(key, "dol")) {
363         tc->do_l = (val && strcmp(val, "0"));
364     } else if (!strcmp(key, "doo")) {
365         tc->do_o = (val && strcmp(val, "0"));
366     }
369 static void
370 sp_tweak_extinput(SPTweakContext *tc, GdkEvent *event)
372     if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &tc->pressure))
373         tc->pressure = CLAMP (tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
374     else
375         tc->pressure = TC_DEFAULT_PRESSURE;
378 double
379 get_dilate_radius (SPTweakContext *tc)
381     // 10 times the pen width:
382     return 500 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom();
385 double
386 get_path_force (SPTweakContext *tc)
388     double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE)
389         /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom());
390     if (force > 3) {
391         force += 4 * (force - 3);
392     }
393     return force * tc->force;
396 double
397 get_move_force (SPTweakContext *tc)
399     double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE);
400     return force * tc->force;
403 bool
404 sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, Geom::Point p, Geom::Point vector, gint mode, double radius, double force, double fidelity, bool reverse)
406     bool did = false;
408     if (SP_IS_BOX3D(item) && !is_transform_mode(mode) && !is_color_mode(mode)) {
409         // convert 3D boxes to ordinary groups before tweaking their shapes
410         item = SP_ITEM(box3d_convert_to_group(SP_BOX3D(item)));
411         selection->add(item);
412     }
414     if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item)) {
415         for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
416             if (SP_IS_ITEM(child)) {
417                 if (sp_tweak_dilate_recursive (selection, SP_ITEM(child), p, vector, mode, radius, force, fidelity, reverse))
418                     did = true;
419             }
420         }
422     } else {
423         if (mode == TWEAK_MODE_MOVE) {
425             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
426             if (a) {
427                 double x = Geom::L2(a->midpoint() - p)/radius;
428                 if (a->contains(p)) x = 0;
429                 if (x < 1) {
430                     Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * vector;
431                     sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
432                     did = true;
433                 }
434             }
436         } else if (mode == TWEAK_MODE_MOVE_IN_OUT) {
438             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
439             if (a) {
440                 double x = Geom::L2(a->midpoint() - p)/radius;
441                 if (a->contains(p)) x = 0;
442                 if (x < 1) {
443                     Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * 
444                         (reverse? (a->midpoint() - p) : (p - a->midpoint()));
445                     sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
446                     did = true;
447                 }
448             }
450         } else if (mode == TWEAK_MODE_MOVE_JITTER) {
452             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
453             if (a) {
454                 double dp = g_random_double_range(0, M_PI*2);
455                 double dr = g_random_double_range(0, radius);
456                 double x = Geom::L2(a->midpoint() - p)/radius;
457                 if (a->contains(p)) x = 0;
458                 if (x < 1) {
459                     Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * Geom::Point(cos(dp)*dr, sin(dp)*dr);
460                     sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
461                     did = true;
462                 }
463             }
465         } else if (mode == TWEAK_MODE_SCALE) {
467             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
468             if (a) {
469                 double x = Geom::L2(a->midpoint() - p)/radius;
470                 if (a->contains(p)) x = 0;
471                 if (x < 1) {
472                     double scale = 1 + (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1);
473                     sp_item_scale_rel(item, Geom::Scale(scale, scale));
474                     did = true;
475                 }
476             }
478         } else if (mode == TWEAK_MODE_ROTATE) {
480             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
481             if (a) {
482                 double x = Geom::L2(a->midpoint() - p)/radius;
483                 if (a->contains(p)) x = 0;
484                 if (x < 1) {
485                     double angle = (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1) * M_PI;
486                     sp_item_rotate_rel(item, Geom::Rotate(angle));
487                     did = true;
488                 }
489             }
491         } else if (mode == TWEAK_MODE_MORELESS) {
493             boost::optional<Geom::Rect> a = item->getBounds(sp_item_i2doc_affine(item));
494             if (a) {
495                 double x = Geom::L2(a->midpoint() - p)/radius;
496                 if (a->contains(p)) x = 0;
497                 if (x < 1) {
498                     double prob = force * 0.5 * (cos(M_PI * x) + 1);
499                     double chance = g_random_double_range(0, 1);
500                     if (chance <= prob) {
501                         if (reverse) { // delete
502                             sp_object_ref(SP_OBJECT(item), NULL);
503                             SP_OBJECT(item)->deleteObject(true, true);
504                             sp_object_unref(SP_OBJECT(item), NULL);
505                         } else { // duplicate
506                             SPDocument *doc = SP_OBJECT_DOCUMENT(item);
507                             Inkscape::XML::Document* xml_doc = sp_document_repr_doc(doc);
508                             Inkscape::XML::Node *old_repr = SP_OBJECT_REPR(item);
509                             SPObject *old_obj = doc->getObjectByRepr(old_repr);
510                             Inkscape::XML::Node *parent = old_repr->parent();
511                             Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
512                             parent->appendChild(copy);
513                             SPObject *new_obj = doc->getObjectByRepr(copy);
514                             if (selection->includes(old_obj)) {
515                                 selection->add(new_obj);
516                             }
517                             Inkscape::GC::release(copy);
518                         }
519                     }
520                 }
521             }
523         } else if (SP_IS_PATH(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
525         Inkscape::XML::Node *newrepr = NULL;
526         gint pos = 0;
527         Inkscape::XML::Node *parent = NULL;
528         char const *id = NULL;
529         if (!SP_IS_PATH(item)) {
530             newrepr = sp_selected_item_to_curved_repr(item, 0);
531             if (!newrepr)
532                 return false;
534             // remember the position of the item
535             pos = SP_OBJECT_REPR(item)->position();
536             // remember parent
537             parent = SP_OBJECT_REPR(item)->parent();
538             // remember id
539             id = SP_OBJECT_REPR(item)->attribute("id");
540         }
543         // skip those paths whose bboxes are entirely out of reach with our radius
544         boost::optional<Geom::Rect> bbox = item->getBounds(sp_item_i2doc_affine(item));
545         if (bbox) {
546             bbox->expandBy(radius);
547             if (!bbox->contains(p)) {
548                 return false;
549             }
550         }
552         Path *orig = Path_for_item(item, false);
553         if (orig == NULL) {
554             return false;
555         }
557         Path *res = new Path;
558         res->SetBackData(false);
560         Shape *theShape = new Shape;
561         Shape *theRes = new Shape;
562         Geom::Matrix i2doc(sp_item_i2doc_affine(item));
564         orig->ConvertWithBackData((0.08 - (0.07 * fidelity)) / i2doc.descrim()); // default 0.059
565         orig->Fill(theShape, 0);
567         SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
568         gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
569         if (val && strcmp(val, "nonzero") == 0)
570         {
571             theRes->ConvertToShape(theShape, fill_nonZero);
572         }
573         else if (val && strcmp(val, "evenodd") == 0)
574         {
575             theRes->ConvertToShape(theShape, fill_oddEven);
576         }
577         else
578         {
579             theRes->ConvertToShape(theShape, fill_nonZero);
580         }
582         if (Geom::L2(vector) != 0)
583             vector = 1/Geom::L2(vector) * vector;
585         bool did_this = false;
586         if (mode == TWEAK_MODE_SHRINK_GROW) {
587             if (theShape->MakeTweak(tweak_mode_grow, theRes,
588                                  reverse? force : -force,
589                                  join_straight, 4.0,
590                                  true, p, Geom::Point(0,0), radius, &i2doc) == 0) // 0 means the shape was actually changed
591               did_this = true;
592         } else if (mode == TWEAK_MODE_ATTRACT_REPEL) {
593             if (theShape->MakeTweak(tweak_mode_repel, theRes,
594                                  reverse? force : -force,
595                                  join_straight, 4.0,
596                                  true, p, Geom::Point(0,0), radius, &i2doc) == 0)
597               did_this = true;
598         } else if (mode == TWEAK_MODE_PUSH) {
599             if (theShape->MakeTweak(tweak_mode_push, theRes,
600                                  1.0,
601                                  join_straight, 4.0,
602                                  true, p, force*2*vector, radius, &i2doc) == 0)
603               did_this = true;
604         } else if (mode == TWEAK_MODE_ROUGHEN) {
605             if (theShape->MakeTweak(tweak_mode_roughen, theRes,
606                                  force,
607                                  join_straight, 4.0,
608                                  true, p, Geom::Point(0,0), radius, &i2doc) == 0)
609               did_this = true;
610         }
612         // the rest only makes sense if we actually changed the path
613         if (did_this) {
614             theRes->ConvertToShape(theShape, fill_positive);
616             res->Reset();
617             theRes->ConvertToForme(res);
619             double th_max = (0.6 - 0.59*sqrt(fidelity)) / i2doc.descrim();
620             double threshold = MAX(th_max, th_max*force);
621             res->ConvertEvenLines(threshold);
622             res->Simplify(threshold / (selection->desktop()->current_zoom()));
624             if (newrepr) { // converting to path, need to replace the repr
625                 bool is_selected = selection->includes(item);
626                 if (is_selected)
627                     selection->remove(item);
629                 // It's going to resurrect, so we delete without notifying listeners.
630                 SP_OBJECT(item)->deleteObject(false);
632                 // restore id
633                 newrepr->setAttribute("id", id);
634                 // add the new repr to the parent
635                 parent->appendChild(newrepr);
636                 // move to the saved position
637                 newrepr->setPosition(pos > 0 ? pos : 0);
639                 if (is_selected)
640                     selection->add(newrepr);
641             }
643             if (res->descr_cmd.size() > 1) {
644                 gchar *str = res->svg_dump_path();
645                 if (newrepr) {
646                     newrepr->setAttribute("d", str);
647                 } else {
648                     if (SP_IS_LPE_ITEM(item) && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(item))) {
649                         SP_OBJECT_REPR(item)->setAttribute("inkscape:original-d", str);
650                     } else {
651                         SP_OBJECT_REPR(item)->setAttribute("d", str);
652                     }
653                 }
654                 g_free(str);
655             } else {
656                 // TODO: if there's 0 or 1 node left, delete this path altogether
657             }
659             if (newrepr) {
660                 Inkscape::GC::release(newrepr);
661                 newrepr = NULL;
662             }
663         }
665         delete theShape;
666         delete theRes;
667         delete orig;
668         delete res;
670         if (did_this)
671             did = true;
672     }
674     }
676     return did;
679 void
680 tweak_colorpaint (float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l)
682     float rgb_g[3];
684     if (!do_h || !do_s || !do_l) {
685         float hsl_g[3];
686         sp_color_rgb_to_hsl_floatv (hsl_g, SP_RGBA32_R_F(goal), SP_RGBA32_G_F(goal), SP_RGBA32_B_F(goal));
687         float hsl_c[3];
688         sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]);
689         if (!do_h)
690             hsl_g[0] = hsl_c[0];
691         if (!do_s)
692             hsl_g[1] = hsl_c[1];
693         if (!do_l)
694             hsl_g[2] = hsl_c[2];
695         sp_color_hsl_to_rgb_floatv (rgb_g, hsl_g[0], hsl_g[1], hsl_g[2]);
696     } else {
697         rgb_g[0] = SP_RGBA32_R_F(goal);
698         rgb_g[1] = SP_RGBA32_G_F(goal);
699         rgb_g[2] = SP_RGBA32_B_F(goal);
700     }
702     for (int i = 0; i < 3; i++) {
703         double d = rgb_g[i] - color[i];
704         color[i] += d * force;
705     }
708 void
709 tweak_colorjitter (float *color, double force, bool do_h, bool do_s, bool do_l)
711     float hsl_c[3];
712     sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]);
714     if (do_h) {
715         hsl_c[0] += g_random_double_range(-0.5, 0.5) * force;
716         if (hsl_c[0] > 1)
717             hsl_c[0] -= 1;
718         if (hsl_c[0] < 0)
719             hsl_c[0] += 1;
720     }
721     if (do_s) {
722         hsl_c[1] += g_random_double_range(-hsl_c[1], 1 - hsl_c[1]) * force;
723     }
724     if (do_l) {
725         hsl_c[2] += g_random_double_range(-hsl_c[2], 1 - hsl_c[2]) * force;
726     }
728     sp_color_hsl_to_rgb_floatv (color, hsl_c[0], hsl_c[1], hsl_c[2]);
731 void
732 tweak_color (guint mode, float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l)
734     if (mode == TWEAK_MODE_COLORPAINT) {
735         tweak_colorpaint (color, goal, force, do_h, do_s, do_l);
736     } else if (mode == TWEAK_MODE_COLORJITTER) {
737         tweak_colorjitter (color, force, do_h, do_s, do_l);
738     }
741 void
742 tweak_opacity (guint mode, SPIScale24 *style_opacity, double opacity_goal, double force)
744     double opacity = SP_SCALE24_TO_FLOAT (style_opacity->value);
746     if (mode == TWEAK_MODE_COLORPAINT) {
747         double d = opacity_goal - opacity;
748         opacity += d * force;
749     } else if (mode == TWEAK_MODE_COLORJITTER) {
750         opacity += g_random_double_range(-opacity, 1 - opacity) * force;
751     }
753     style_opacity->value = SP_SCALE24_FROM_FLOAT(opacity);
757 double
758 tweak_profile (double dist, double radius)
760     if (radius == 0)
761         return 0;
762     double x = dist / radius;
763     double alpha = 1;
764     if (x >= 1) {
765         return 0;
766     } else if (x <= 0) {
767         return 1;
768     } else {
769         return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
770     }
773 void
774 tweak_colors_in_gradient (SPItem *item, bool fill_or_stroke,
775                           guint32 const rgb_goal, NR::Point p_w, double radius, double force, guint mode,
776                           bool do_h, bool do_s, bool do_l, bool /*do_o*/)
778     SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
780     if (!gradient || !SP_IS_GRADIENT(gradient))
781         return;
783     NR::Matrix i2d (sp_item_i2doc_affine (item));
784     NR::Point p = p_w * i2d.inverse();
785     p *= (gradient->gradientTransform).inverse();
786     // now p is in gradient's original coordinates
788     double pos = 0;
789     double r = 0;
790     if (SP_IS_LINEARGRADIENT(gradient)) {
791         SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
793         NR::Point p1(lg->x1.computed, lg->y1.computed);
794         NR::Point p2(lg->x2.computed, lg->y2.computed);
795         NR::Point pdiff(p2 - p1);
796         double vl = NR::L2(pdiff);
798         // This is the matrix which moves and rotates the gradient line
799         // so it's oriented along the X axis:
800         NR::Matrix norm = NR::Matrix(Geom::Translate(-p1)) * NR::Matrix(Geom::Rotate(-atan2(pdiff[NR::Y], pdiff[NR::X])));
802         // Transform the mouse point by it to find out its projection onto the gradient line:
803         NR::Point pnorm = p * norm;
805         // Scale its X coordinate to match the length of the gradient line:
806         pos = pnorm[NR::X] / vl;
807         // Calculate radius in lenfth-of-gradient-line units
808         r = radius / vl;
810     } else if (SP_IS_RADIALGRADIENT(gradient)) {
811         SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
812         NR::Point c (rg->cx.computed, rg->cy.computed);
813         pos = NR::L2(p - c) / rg->r.computed;
814         r = radius / rg->r.computed;
815     }
817     // Normalize pos to 0..1, taking into accound gradient spread:
818     double pos_e = pos;
819     if (gradient->spread == SP_GRADIENT_SPREAD_PAD) {
820         if (pos > 1)
821             pos_e = 1;
822         if (pos < 0)
823             pos_e = 0;
824     } else if (gradient->spread == SP_GRADIENT_SPREAD_REPEAT) {
825         if (pos > 1 || pos < 0)
826             pos_e = pos - floor(pos);
827     } else if (gradient->spread == SP_GRADIENT_SPREAD_REFLECT) {
828         if (pos > 1 || pos < 0) {
829             bool odd = ((int)(floor(pos)) % 2 == 1);
830             pos_e = pos - floor(pos);
831             if (odd)
832                 pos_e = 1 - pos_e;
833         }
834     }
836     SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
838     double offset_l = 0;
839     double offset_h = 0;
840     SPObject *child_prev = NULL;
841     for (SPObject *child = sp_object_first_child(vector);
842          child != NULL; child = SP_OBJECT_NEXT(child)) {
843         if (!SP_IS_STOP(child))
844             continue;
845         SPStop *stop = SP_STOP (child);
847         offset_h = stop->offset;
849         if (child_prev) {
851             if (offset_h - offset_l > r && pos_e >= offset_l && pos_e <= offset_h) {
852                 // the summit falls in this interstop, and the radius is small,
853                 // so it only affects the ends of this interstop;
854                 // distribute the force between the two endstops so that they
855                 // get all the painting even if they are not touched by the brush
856                 tweak_color (mode, stop->specified_color.v.c, rgb_goal,
857                                   force * (pos_e - offset_l) / (offset_h - offset_l),
858                                   do_h, do_s, do_l);
859                 tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal,
860                                   force * (offset_h - pos_e) / (offset_h - offset_l),
861                                   do_h, do_s, do_l);
862                 SP_OBJECT(stop)->updateRepr();
863                 child_prev->updateRepr();
864                 break;
865             } else {
866                 // wide brush, may affect more than 2 stops,
867                 // paint each stop by the force from the profile curve
868                 if (offset_l <= pos_e && offset_l > pos_e - r) {
869                     tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal,
870                                  force * tweak_profile (fabs (pos_e - offset_l), r),
871                                  do_h, do_s, do_l);
872                     child_prev->updateRepr();
873                 }
875                 if (offset_h >= pos_e && offset_h < pos_e + r) {
876                     tweak_color (mode, stop->specified_color.v.c, rgb_goal,
877                                  force * tweak_profile (fabs (pos_e - offset_h), r),
878                                  do_h, do_s, do_l);
879                     SP_OBJECT(stop)->updateRepr();
880                 }
881             }
882         }
884         offset_l = offset_h;
885         child_prev = child;
886     }
889 bool
890 sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point,
891                           guint32 fill_goal, bool do_fill,
892                           guint32 stroke_goal, bool do_stroke,
893                           float opacity_goal, bool do_opacity,
894                           bool do_blur, bool reverse,
895                           NR::Point p, double radius, double force,
896                           bool do_h, bool do_s, bool do_l, bool do_o)
898     bool did = false;
900     if (SP_IS_GROUP(item)) {
901         for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
902             if (SP_IS_ITEM(child)) {
903                 if (sp_tweak_color_recursive (mode, SP_ITEM(child), item_at_point,
904                                           fill_goal, do_fill,
905                                           stroke_goal, do_stroke,
906                                           opacity_goal, do_opacity,
907                                           do_blur, reverse,
908                                           p, radius, force, do_h, do_s, do_l, do_o))
909                     did = true;
910             }
911         }
913     } else {
914         SPStyle *style = SP_OBJECT_STYLE(item);
915         if (!style) {
916             return false;
917         }
918         boost::optional<Geom::Rect> bbox = item->getBounds(sp_item_i2doc_affine(item),
919                                                         SPItem::GEOMETRIC_BBOX);
920         if (!bbox) {
921             return false;
922         }
924         Geom::Rect brush(p - Geom::Point(radius, radius), p + Geom::Point(radius, radius));
926         NR::Point center = bbox->midpoint();
927         double this_force;
929 // if item == item_at_point, use max force
930         if (item == item_at_point) {
931             this_force = force;
932 // else if no overlap of bbox and brush box, skip:
933         } else if (!bbox->intersects(brush)) {
934             return false;
935 //TODO:
936 // else if object > 1.5 brush: test 4/8/16 points in the brush on hitting the object, choose max
937         //} else if (bbox->maxExtent() > 3 * radius) {
938         //}
939 // else if object > 0.5 brush: test 4 corners of bbox and center on being in the brush, choose max
940 // else if still smaller, then check only the object center:
941         } else {
942             this_force = force * tweak_profile (NR::L2 (p - center), radius);
943         }
945         if (this_force > 0.002) {
947             if (do_blur) {
948                 boost::optional<Geom::Rect> bbox = item->getBounds(sp_item_i2doc_affine(item),
949                                                         SPItem::GEOMETRIC_BBOX);
950                 if (!bbox) {
951                     return did;
952                 }
954                 double blur_now = 0;
955                 Geom::Matrix i2d = sp_item_i2d_affine (item);
956                 if (style->filter.set && style->getFilter()) {
957                     //cycle through filter primitives
958                     SPObject *primitive_obj = style->getFilter()->children;
959                     while (primitive_obj) {
960                         if (SP_IS_FILTER_PRIMITIVE(primitive_obj)) {
961                             SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(primitive_obj);
962                             //if primitive is gaussianblur
963                             if(SP_IS_GAUSSIANBLUR(primitive)) {
964                                 SPGaussianBlur * spblur = SP_GAUSSIANBLUR(primitive);
965                                 float num = spblur->stdDeviation.getNumber();
966                                 blur_now += num * i2d.descrim(); // sum all blurs in the filter
967                             }
968                         }
969                         primitive_obj = primitive_obj->next;
970                     }
971                 }
972                 double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y];
973                 blur_now = blur_now / perimeter;
975                 double blur_new;
976                 if (reverse) 
977                     blur_new = blur_now - 0.06 * force;
978                 else
979                     blur_new = blur_now + 0.06 * force;
980                 if (blur_new < 0.0005 && blur_new < blur_now) {
981                     blur_new = 0;
982                 }
984                 if (blur_new == 0) {
985                     remove_filter(item, false);
986                 } else {
987                     double radius = blur_new * perimeter;
988                     SPFilter *filter = modify_filter_gaussian_blur_from_item(SP_OBJECT_DOCUMENT(item), item, radius);
990                     sp_style_set_property_url(item, "filter", filter, false);
991                 }
992                 return true; // do not do colors, blur is a separate mode
993             }
995             if (do_fill) {
996                 if (style->fill.isPaintserver()) {
997                     tweak_colors_in_gradient (item, true, fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o);
998                     did = true;
999                 } else if (style->fill.isColor()) {
1000                     tweak_color (mode, style->fill.value.color.v.c, fill_goal, this_force, do_h, do_s, do_l);
1001                     item->updateRepr();
1002                     did = true;
1003                 }
1004             }
1005             if (do_stroke) {
1006                 if (style->stroke.isPaintserver()) {
1007                     tweak_colors_in_gradient (item, false, stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o);
1008                     did = true;
1009                 } else if (style->stroke.isColor()) {
1010                     tweak_color (mode, style->stroke.value.color.v.c, stroke_goal, this_force, do_h, do_s, do_l);
1011                     item->updateRepr();
1012                     did = true;
1013                 }
1014             }
1015             if (do_opacity && do_o) {
1016                 tweak_opacity (mode, &style->opacity, opacity_goal, this_force);
1017             }
1018         }
1019     }
1021     return did;
1025 bool
1026 sp_tweak_dilate (SPTweakContext *tc, NR::Point event_p, NR::Point p, NR::Point vector, bool reverse)
1028     Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop);
1029     SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop;
1031     if (selection->isEmpty()) {
1032         return false;
1033     }
1035     bool did = false;
1036     double radius = get_dilate_radius(tc);
1038     SPItem *item_at_point = SP_EVENT_CONTEXT(tc)->desktop->item_at_point(event_p, TRUE);
1040     bool do_fill = false, do_stroke = false, do_opacity = false;
1041     guint32 fill_goal = sp_desktop_get_color_tool(desktop, "tools.tweak", true, &do_fill);
1042     guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "tools.tweak", false, &do_stroke);
1043     double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "tools.tweak", &do_opacity);
1044     if (reverse) {
1045         fill_goal = SP_RGBA32_U_COMPOSE(
1046             (255 - SP_RGBA32_R_U(fill_goal)),
1047             (255 - SP_RGBA32_G_U(fill_goal)),
1048             (255 - SP_RGBA32_B_U(fill_goal)),
1049             (255 - SP_RGBA32_A_U(fill_goal)));
1050         stroke_goal = SP_RGBA32_U_COMPOSE(
1051             (255 - SP_RGBA32_R_U(stroke_goal)),
1052             (255 - SP_RGBA32_G_U(stroke_goal)),
1053             (255 - SP_RGBA32_B_U(stroke_goal)),
1054             (255 - SP_RGBA32_A_U(stroke_goal)));
1055         opacity_goal = 1 - opacity_goal;
1056     }
1058     double path_force = get_path_force(tc);
1059     if (radius == 0 || path_force == 0) {
1060         return false;
1061     }
1062     double move_force = get_move_force(tc);
1063     double color_force = MIN(sqrt(path_force)/20.0, 1);
1065     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
1066          items != NULL;
1067          items = items->next) {
1069         SPItem *item = (SPItem *) items->data;
1071         if (is_color_mode (tc->mode)) {
1072             if (do_fill || do_stroke || do_opacity) {
1073                 if (sp_tweak_color_recursive (tc->mode, item, item_at_point,
1074                                           fill_goal, do_fill,
1075                                           stroke_goal, do_stroke,
1076                                           opacity_goal, do_opacity,
1077                                           tc->mode == TWEAK_MODE_BLUR, reverse,
1078                                           p, radius, color_force, tc->do_h, tc->do_s, tc->do_l, tc->do_o))
1079                 did = true;
1080             }
1081         } else if (is_transform_mode(tc->mode)) {
1082             if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, move_force, tc->fidelity, reverse))
1083                 did = true;
1084         } else {
1085             if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, path_force, tc->fidelity, reverse))
1086                 did = true;
1087         }
1088     }
1090     return did;
1093 void
1094 sp_tweak_update_area (SPTweakContext *tc)
1096         double radius = get_dilate_radius(tc);
1097         NR::Matrix const sm (Geom::Scale(radius, radius) * Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point()));
1098         sp_canvas_item_affine_absolute(tc->dilate_area, sm);
1099         sp_canvas_item_show(tc->dilate_area);
1102 void
1103 sp_tweak_switch_mode (SPTweakContext *tc, gint mode, bool with_shift)
1105     SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode);
1106     // need to set explicitly, because the prefs may not have changed by the previous
1107     tc->mode = mode;
1108     sp_tweak_update_cursor (tc, with_shift);
1111 void
1112 sp_tweak_switch_mode_temporarily (SPTweakContext *tc, gint mode, bool with_shift)
1114    // Juggling about so that prefs have the old value but tc->mode and the button show new mode:
1115    gint now_mode = prefs_get_int_attribute("tools.tweak", "mode", 0);
1116    SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode);
1117    // button has changed prefs, restore
1118    prefs_set_int_attribute("tools.tweak", "mode", now_mode);
1119    // changing prefs changed tc->mode, restore back :)
1120    tc->mode = mode;
1121    sp_tweak_update_cursor (tc, with_shift);
1124 gint
1125 sp_tweak_context_root_handler(SPEventContext *event_context,
1126                                   GdkEvent *event)
1128     SPTweakContext *tc = SP_TWEAK_CONTEXT(event_context);
1129     SPDesktop *desktop = event_context->desktop;
1131     gint ret = FALSE;
1133     switch (event->type) {
1134         case GDK_ENTER_NOTIFY:
1135             sp_canvas_item_show(tc->dilate_area);
1136             break;
1137         case GDK_LEAVE_NOTIFY:
1138             sp_canvas_item_hide(tc->dilate_area);
1139             break;
1140         case GDK_BUTTON_PRESS:
1141             if (event->button.button == 1 && !event_context->space_panning) {
1143                 if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false) {
1144                     return TRUE;
1145                 }
1147                 NR::Point const button_w(event->button.x,
1148                                          event->button.y);
1149                 NR::Point const button_dt(desktop->w2d(button_w));
1150                 tc->last_push = desktop->dt2doc(button_dt);
1152                 sp_tweak_extinput(tc, event);
1154                 sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
1155                 tc->is_drawing = true;
1156                 tc->is_dilating = true;
1157                 tc->has_dilated = false;
1159                 ret = TRUE;
1160             }
1161             break;
1162         case GDK_MOTION_NOTIFY:
1163         {
1164             NR::Point const motion_w(event->motion.x,
1165                                      event->motion.y);
1166             NR::Point motion_dt(desktop->w2d(motion_w));
1167             NR::Point motion_doc(desktop->dt2doc(motion_dt));
1168             sp_tweak_extinput(tc, event);
1170             // draw the dilating cursor
1171                 double radius = get_dilate_radius(tc);
1172                 NR::Matrix const sm (Geom::Scale(radius, radius) * Geom::Translate(desktop->w2d(motion_w)));
1173                 sp_canvas_item_affine_absolute(tc->dilate_area, sm);
1174                 sp_canvas_item_show(tc->dilate_area);
1176                 guint num = 0;
1177                 if (!desktop->selection->isEmpty()) {
1178                     num = g_slist_length((GSList *) desktop->selection->itemList());
1179                 }
1180                 if (num == 0) {
1181                     tc->_message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to tweak."));
1182                 }
1184             // dilating:
1185             if (tc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
1186                 sp_tweak_dilate (tc, motion_w, motion_doc, motion_doc - tc->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
1187                 //tc->last_push = motion_doc;
1188                 tc->has_dilated = true;
1189                 // it's slow, so prevent clogging up with events
1190                 gobble_motion_events(GDK_BUTTON1_MASK);
1191                 return TRUE;
1192             }
1194         }
1195         break;
1198     case GDK_BUTTON_RELEASE:
1199     {
1200         NR::Point const motion_w(event->button.x, event->button.y);
1201         NR::Point const motion_dt(desktop->w2d(motion_w));
1203         sp_canvas_end_forced_full_redraws(desktop->canvas);
1204         tc->is_drawing = false;
1206         if (tc->is_dilating && event->button.button == 1 && !event_context->space_panning) {
1207             if (!tc->has_dilated) {
1208                 // if we did not rub, do a light tap
1209                 tc->pressure = 0.03;
1210                 sp_tweak_dilate (tc, motion_w, desktop->dt2doc(motion_dt), NR::Point(0,0), MOD__SHIFT);
1211             }
1212             tc->is_dilating = false;
1213             tc->has_dilated = false;
1214             switch (tc->mode) {
1215                 case TWEAK_MODE_MOVE:
1216                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1217                                      SP_VERB_CONTEXT_TWEAK, _("Move tweak"));
1218                     break;
1219                 case TWEAK_MODE_MOVE_IN_OUT:
1220                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1221                                      SP_VERB_CONTEXT_TWEAK, _("Move in/out tweak"));
1222                     break;
1223                 case TWEAK_MODE_MOVE_JITTER:
1224                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1225                                      SP_VERB_CONTEXT_TWEAK, _("Move jitter tweak"));
1226                     break;
1227                 case TWEAK_MODE_SCALE:
1228                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1229                                      SP_VERB_CONTEXT_TWEAK, _("Scale tweak"));
1230                     break;
1231                 case TWEAK_MODE_ROTATE:
1232                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1233                                      SP_VERB_CONTEXT_TWEAK, _("Rotate tweak"));
1234                     break;
1235                 case TWEAK_MODE_MORELESS:
1236                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1237                                      SP_VERB_CONTEXT_TWEAK, _("Duplicate/delete tweak"));
1238                     break;
1239                 case TWEAK_MODE_PUSH:
1240                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1241                                      SP_VERB_CONTEXT_TWEAK, _("Push path tweak"));
1242                     break;
1243                 case TWEAK_MODE_SHRINK_GROW:
1244                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1245                                      SP_VERB_CONTEXT_TWEAK, _("Shrink/grow path tweak"));
1246                     break;
1247                 case TWEAK_MODE_ATTRACT_REPEL:
1248                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1249                                      SP_VERB_CONTEXT_TWEAK, _("Attract/repel path tweak"));
1250                     break;
1251                 case TWEAK_MODE_ROUGHEN:
1252                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1253                                      SP_VERB_CONTEXT_TWEAK, _("Roughen path tweak"));
1254                     break;
1255                 case TWEAK_MODE_COLORPAINT:
1256                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1257                                      SP_VERB_CONTEXT_TWEAK, _("Color paint tweak"));
1258                     break;
1259                 case TWEAK_MODE_COLORJITTER:
1260                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1261                                      SP_VERB_CONTEXT_TWEAK, _("Color jitter tweak"));
1262                     break;
1263                 case TWEAK_MODE_BLUR:
1264                     sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop),
1265                                      SP_VERB_CONTEXT_TWEAK, _("Blur tweak"));
1266                     break;
1267             }
1268         }
1269         break;
1270     }
1272     case GDK_KEY_PRESS:
1273         switch (get_group0_keyval (&event->key)) {
1274         case GDK_m:
1275         case GDK_M:
1276         case GDK_0:
1277             if (MOD__SHIFT_ONLY) {
1278                 sp_tweak_switch_mode(tc, TWEAK_MODE_MOVE, MOD__SHIFT);
1279                 ret = TRUE;
1280             }
1281             break;
1282         case GDK_i:
1283         case GDK_I:
1284         case GDK_1:
1285             if (MOD__SHIFT_ONLY) {
1286                 sp_tweak_switch_mode(tc, TWEAK_MODE_MOVE_IN_OUT, MOD__SHIFT);
1287                 ret = TRUE;
1288             }
1289             break;
1290         case GDK_z:
1291         case GDK_Z:
1292         case GDK_2:
1293             if (MOD__SHIFT_ONLY) {
1294                 sp_tweak_switch_mode(tc, TWEAK_MODE_MOVE_JITTER, MOD__SHIFT);
1295                 ret = TRUE;
1296             }
1297             break;
1298         case GDK_less:
1299         case GDK_comma:
1300         case GDK_greater:
1301         case GDK_period:
1302         case GDK_3:
1303             if (MOD__SHIFT_ONLY) {
1304                 sp_tweak_switch_mode(tc, TWEAK_MODE_SCALE, MOD__SHIFT);
1305                 ret = TRUE;
1306             }
1307             break;
1308         case GDK_bracketright:
1309         case GDK_bracketleft:
1310         case GDK_4:
1311             if (MOD__SHIFT_ONLY) {
1312                 sp_tweak_switch_mode(tc, TWEAK_MODE_ROTATE, MOD__SHIFT);
1313                 ret = TRUE;
1314             }
1315             break;
1316         case GDK_d:
1317         case GDK_D:
1318         case GDK_5:
1319             if (MOD__SHIFT_ONLY) {
1320                 sp_tweak_switch_mode(tc, TWEAK_MODE_MORELESS, MOD__SHIFT);
1321                 ret = TRUE;
1322             }
1323             break;
1324         case GDK_p:
1325         case GDK_P:
1326         case GDK_6:
1327             if (MOD__SHIFT_ONLY) {
1328                 sp_tweak_switch_mode(tc, TWEAK_MODE_PUSH, MOD__SHIFT);
1329                 ret = TRUE;
1330             }
1331             break;
1332         case GDK_s:
1333         case GDK_S:
1334         case GDK_7:
1335             if (MOD__SHIFT_ONLY) {
1336                 sp_tweak_switch_mode(tc, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT);
1337                 ret = TRUE;
1338             }
1339             break;
1340         case GDK_a:
1341         case GDK_A:
1342         case GDK_8:
1343             if (MOD__SHIFT_ONLY) {
1344                 sp_tweak_switch_mode(tc, TWEAK_MODE_ATTRACT_REPEL, MOD__SHIFT);
1345                 ret = TRUE;
1346             }
1347             break;
1348         case GDK_r:
1349         case GDK_R:
1350         case GDK_9:
1351             if (MOD__SHIFT_ONLY) {
1352                 sp_tweak_switch_mode(tc, TWEAK_MODE_ROUGHEN, MOD__SHIFT);
1353                 ret = TRUE;
1354             }
1355             break;
1356         case GDK_c:
1357         case GDK_C:
1358             if (MOD__SHIFT_ONLY) {
1359                 sp_tweak_switch_mode(tc, TWEAK_MODE_COLORPAINT, MOD__SHIFT);
1360                 ret = TRUE;
1361             }
1362             break;
1363         case GDK_j:
1364         case GDK_J:
1365             if (MOD__SHIFT_ONLY) {
1366                 sp_tweak_switch_mode(tc, TWEAK_MODE_COLORJITTER, MOD__SHIFT);
1367                 ret = TRUE;
1368             }
1369             break;
1370         case GDK_b:
1371         case GDK_B:
1372             if (MOD__SHIFT_ONLY) {
1373                 sp_tweak_switch_mode(tc, TWEAK_MODE_BLUR, MOD__SHIFT);
1374                 ret = TRUE;
1375             }
1376             break;
1378         case GDK_Up:
1379         case GDK_KP_Up:
1380             if (!MOD__CTRL_ONLY) {
1381                 tc->force += 0.05;
1382                 if (tc->force > 1.0)
1383                     tc->force = 1.0;
1384                 desktop->setToolboxAdjustmentValue ("tweak-force", tc->force * 100);
1385                 ret = TRUE;
1386             }
1387             break;
1388         case GDK_Down:
1389         case GDK_KP_Down:
1390             if (!MOD__CTRL_ONLY) {
1391                 tc->force -= 0.05;
1392                 if (tc->force < 0.0)
1393                     tc->force = 0.0;
1394                 desktop->setToolboxAdjustmentValue ("tweak-force", tc->force * 100);
1395                 ret = TRUE;
1396             }
1397             break;
1398         case GDK_Right:
1399         case GDK_KP_Right:
1400             if (!MOD__CTRL_ONLY) {
1401                 tc->width += 0.01;
1402                 if (tc->width > 1.0)
1403                     tc->width = 1.0;
1404                 desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100); // the same spinbutton is for alt+x
1405                 sp_tweak_update_area(tc);
1406                 ret = TRUE;
1407             }
1408             break;
1409         case GDK_Left:
1410         case GDK_KP_Left:
1411             if (!MOD__CTRL_ONLY) {
1412                 tc->width -= 0.01;
1413                 if (tc->width < 0.01)
1414                     tc->width = 0.01;
1415                 desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100);
1416                 sp_tweak_update_area(tc);
1417                 ret = TRUE;
1418             }
1419             break;
1420         case GDK_Home:
1421         case GDK_KP_Home:
1422             tc->width = 0.01;
1423             desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100);
1424             sp_tweak_update_area(tc);
1425             ret = TRUE;
1426             break;
1427         case GDK_End:
1428         case GDK_KP_End:
1429             tc->width = 1.0;
1430             desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100);
1431             sp_tweak_update_area(tc);
1432             ret = TRUE;
1433             break;
1434         case GDK_x:
1435         case GDK_X:
1436             if (MOD__ALT_ONLY) {
1437                 desktop->setToolboxFocusTo ("altx-tweak");
1438                 ret = TRUE;
1439             }
1440             break;
1442         case GDK_Shift_L:
1443         case GDK_Shift_R:
1444             sp_tweak_update_cursor(tc, true);
1445             break;
1447         case GDK_Control_L:
1448         case GDK_Control_R:
1449             sp_tweak_switch_mode_temporarily(tc, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT);
1450             break;
1451         default:
1452             break;
1453         }
1454         break;
1456     case GDK_KEY_RELEASE:
1457         switch (get_group0_keyval(&event->key)) {
1458             case GDK_Shift_L:
1459             case GDK_Shift_R:
1460                 sp_tweak_update_cursor(tc, false);
1461                 break;
1462             case GDK_Control_L:
1463             case GDK_Control_R:
1464                 sp_tweak_switch_mode (tc, prefs_get_int_attribute("tools.tweak", "mode", 0), MOD__SHIFT);
1465                 tc->_message_context->clear();
1466                 break;
1467             default:
1468                 sp_tweak_switch_mode (tc, prefs_get_int_attribute("tools.tweak", "mode", 0), MOD__SHIFT);
1469                 break;
1470         }
1472     default:
1473         break;
1474     }
1476     if (!ret) {
1477         if (((SPEventContextClass *) parent_class)->root_handler) {
1478             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
1479         }
1480     }
1482     return ret;
1486 /*
1487   Local Variables:
1488   mode:c++
1489   c-file-style:"stroustrup"
1490   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1491   indent-tabs-mode:nil
1492   fill-column:99
1493   End:
1494 */
1495 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :