1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Gradient drawing and editing tool
4  *
5  * Authors:
6  *   bulia byak <buliabyak@users.sf.net>
7  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 2007 Johan Engelen
11  * Copyright (C) 2005 Authors
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include <glibmm/i18n.h>
17 #include <gdk/gdkkeysyms.h>
18 
19 #include "desktop.h"
20 #include "document-undo.h"
21 #include "document.h"
22 #include "gradient-chemistry.h"
23 #include "gradient-drag.h"
24 #include "include/macros.h"
25 #include "message-context.h"
26 #include "message-stack.h"
27 #include "rubberband.h"
28 #include "selection-chemistry.h"
29 #include "selection.h"
30 #include "snap.h"
31 #include "verbs.h"
32 
33 #include "object/sp-namedview.h"
34 #include "object/sp-stop.h"
35 
36 #include "display/control/canvas-item-curve.h"
37 
38 #include "svg/css-ostringstream.h"
39 
40 #include "ui/tools/gradient-tool.h"
41 
42 using Inkscape::DocumentUndo;
43 
44 namespace Inkscape {
45 namespace UI {
46 namespace Tools {
47 
48 static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint state, guint32 etime);
49 
getPrefsPath()50 const std::string& GradientTool::getPrefsPath() {
51 	return GradientTool::prefsPath;
52 }
53 
54 const std::string GradientTool::prefsPath = "/tools/gradient";
55 
56 
GradientTool()57 GradientTool::GradientTool()
58     : ToolBase("gradient.svg")
59     , cursor_addnode(false)
60     , node_added(false)
61 // TODO: Why are these connections stored as pointers?
62     , selcon(nullptr)
63     , subselcon(nullptr)
64 {
65     // TODO: This value is overwritten in the root handler
66     this->tolerance = 6;
67 }
68 
~GradientTool()69 GradientTool::~GradientTool() {
70     this->enableGrDrag(false);
71 
72     this->selcon->disconnect();
73     delete this->selcon;
74 
75     this->subselcon->disconnect();
76     delete this->subselcon;
77 }
78 
79 // This must match GrPointType enum sp-gradient.h
80 // We should move this to a shared header (can't simply move to gradient.h since that would require
81 // including <glibmm/i18n.h> which messes up "N_" in extensions... argh!).
82 const gchar *gr_handle_descr [] = {
83     N_("Linear gradient <b>start</b>"), //POINT_LG_BEGIN
84     N_("Linear gradient <b>end</b>"),
85     N_("Linear gradient <b>mid stop</b>"),
86     N_("Radial gradient <b>center</b>"),
87     N_("Radial gradient <b>radius</b>"),
88     N_("Radial gradient <b>radius</b>"),
89     N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
90     N_("Radial gradient <b>mid stop</b>"),
91     N_("Radial gradient <b>mid stop</b>"),
92     N_("Mesh gradient <b>corner</b>"),
93     N_("Mesh gradient <b>handle</b>"),
94     N_("Mesh gradient <b>tensor</b>")
95 };
96 
selection_changed(Inkscape::Selection *)97 void GradientTool::selection_changed(Inkscape::Selection*) {
98 
99     GrDrag *drag = _grdrag;
100     Inkscape::Selection *selection = this->desktop->getSelection();
101     if (selection == nullptr) {
102         return;
103     }
104     guint n_obj = (guint) boost::distance(selection->items());
105 
106     if (!drag->isNonEmpty() || selection->isEmpty())
107         return;
108     guint n_tot = drag->numDraggers();
109     guint n_sel = drag->numSelected();
110 
111     //The use of ngettext in the following code is intentional even if the English singular form would never be used
112     if (n_sel == 1) {
113         if (drag->singleSelectedDraggerNumDraggables() == 1) {
114             gchar * message = g_strconcat(
115                 //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message
116                 _("%s selected"),
117                 //TRANSLATORS: Mind the space in front. This is part of a compound message
118                 ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot),
119                 ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
120             message_context->setF(Inkscape::NORMAL_MESSAGE,
121                                   message,_(gr_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj);
122         } else {
123             gchar * message = g_strconcat(
124                 //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count)
125                 ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
126                          "One handle merging %d stops (drag with <b>Shift</b> to separate) selected",drag->singleSelectedDraggerNumDraggables()),
127                 ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot),
128                 ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
129             message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj);
130         }
131     } else if (n_sel > 1) {
132         //TRANSLATORS: The plural refers to number of selected gradient handles. This is part of a compound message (part two indicates selected object count)
133         gchar * message = g_strconcat(ngettext("<b>%d</b> gradient handle selected out of %d","<b>%d</b> gradient handles selected out of %d",n_sel),
134                                       //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message
135                                       ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
136         message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj);
137     } else if (n_sel == 0) {
138         message_context->setF(Inkscape::NORMAL_MESSAGE,
139                               //TRANSLATORS: The plural refers to number of selected objects
140                               ngettext("<b>No</b> gradient handles selected out of %d on %d selected object",
141                                        "<b>No</b> gradient handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj);
142     }
143 }
144 
setup()145 void GradientTool::setup() {
146     ToolBase::setup();
147 
148     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
149 
150     if (prefs->getBool("/tools/gradient/selcue", true)) {
151         this->enableSelectionCue();
152     }
153 
154     this->enableGrDrag();
155     Inkscape::Selection *selection = this->desktop->getSelection();
156 
157     this->selcon = new sigc::connection(selection->connectChanged(
158     	sigc::mem_fun(this, &GradientTool::selection_changed)
159     ));
160 
161     this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged(
162     	sigc::hide(sigc::bind(
163     		sigc::mem_fun(this, &GradientTool::selection_changed),
164     		(Inkscape::Selection*)nullptr
165     	))
166     ));
167 
168     this->selection_changed(selection);
169 }
170 
171 void
sp_gradient_context_select_next(ToolBase * event_context)172 sp_gradient_context_select_next (ToolBase *event_context)
173 {
174     GrDrag *drag = event_context->_grdrag;
175     g_assert (drag);
176 
177     GrDragger *d = drag->select_next();
178 
179     event_context->getDesktop()->scroll_to_point(d->point, 1.0);
180 }
181 
182 void
sp_gradient_context_select_prev(ToolBase * event_context)183 sp_gradient_context_select_prev (ToolBase *event_context)
184 {
185     GrDrag *drag = event_context->_grdrag;
186     g_assert (drag);
187 
188     GrDragger *d = drag->select_prev();
189 
190     event_context->getDesktop()->scroll_to_point(d->point, 1.0);
191 }
192 
193 static SPItem*
sp_gradient_context_is_over_curve(GradientTool * rc,Geom::Point event_p)194 sp_gradient_context_is_over_curve (GradientTool *rc, Geom::Point event_p)
195 {
196     const SPDesktop *desktop = rc->getDesktop();
197 
198     //Translate mouse point into proper coord system: needed later.
199     rc->mousepoint_doc = desktop->w2d(event_p);
200 
201     GrDrag *drag = rc->_grdrag;
202     for (auto curve : drag->item_curves) {
203         if (curve->contains(event_p, rc->tolerance)) {
204             return curve->get_item();
205         }
206     }
207     return nullptr;
208 }
209 
210 static std::vector<Geom::Point>
sp_gradient_context_get_stop_intervals(GrDrag * drag,std::vector<SPStop * > & these_stops,std::vector<SPStop * > & next_stops)211 sp_gradient_context_get_stop_intervals (GrDrag *drag, std::vector<SPStop *> &these_stops, std::vector<SPStop *> &next_stops)
212 {
213     std::vector<Geom::Point> coords;
214 
215     // for all selected draggers
216     for (std::set<GrDragger *>::const_iterator i = drag->selected.begin(); i != drag->selected.end() ; ++i ) {
217         GrDragger *dragger = *i;
218         // remember the coord of the dragger to reselect it later
219         coords.push_back(dragger->point);
220         // for all draggables of dragger
221         for (std::vector<GrDraggable *>::const_iterator j = dragger->draggables.begin(); j != dragger->draggables.end(); ++j) {
222             GrDraggable *d = *j;
223 
224             // find the gradient
225             SPGradient *gradient = getGradient(d->item, d->fill_or_stroke);
226             SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
227 
228             // these draggable types cannot have a next draggabe to insert a stop between them
229             if (d->point_type == POINT_LG_END ||
230                 d->point_type == POINT_RG_FOCUS ||
231                 d->point_type == POINT_RG_R1 ||
232                 d->point_type == POINT_RG_R2) {
233                 continue;
234             }
235 
236             // from draggables to stops
237             SPStop *this_stop = sp_get_stop_i (vector, d->point_i);
238             SPStop *next_stop = this_stop->getNextStop();
239             SPStop *last_stop = sp_last_stop (vector);
240 
241             Inkscape::PaintTarget fs = d->fill_or_stroke;
242             SPItem *item = d->item;
243             gint type = d->point_type;
244             gint p_i = d->point_i;
245 
246             // if there's a next stop,
247             if (next_stop) {
248                 GrDragger *dnext = nullptr;
249                 // find its dragger
250                 // (complex because it may have different types, and because in radial,
251                 // more than one dragger may correspond to a stop, so we must distinguish)
252                 if (type == POINT_LG_BEGIN || type == POINT_LG_MID) {
253                     if (next_stop == last_stop) {
254                         dnext = drag->getDraggerFor(item, POINT_LG_END, p_i+1, fs);
255                     } else {
256                         dnext = drag->getDraggerFor(item, POINT_LG_MID, p_i+1, fs);
257                     }
258                 } else { // radial
259                     if (type == POINT_RG_CENTER || type == POINT_RG_MID1) {
260                         if (next_stop == last_stop) {
261                             dnext = drag->getDraggerFor(item, POINT_RG_R1, p_i+1, fs);
262                         } else {
263                             dnext = drag->getDraggerFor(item, POINT_RG_MID1, p_i+1, fs);
264                         }
265                     }
266                     if ((type == POINT_RG_MID2) ||
267                         (type == POINT_RG_CENTER && dnext && !dnext->isSelected())) {
268                         if (next_stop == last_stop) {
269                             dnext = drag->getDraggerFor(item, POINT_RG_R2, p_i+1, fs);
270                         } else {
271                             dnext = drag->getDraggerFor(item, POINT_RG_MID2, p_i+1, fs);
272                         }
273                     }
274                 }
275 
276                 // if both adjacent draggers selected,
277                 if ((std::find(these_stops.begin(),these_stops.end(),this_stop)==these_stops.end()) && dnext && dnext->isSelected()) {
278 
279                     // remember the coords of the future dragger to select it
280                     coords.push_back(0.5*(dragger->point + dnext->point));
281 
282                     // do not insert a stop now, it will confuse the loop;
283                     // just remember the stops
284                     these_stops.push_back(this_stop);
285                     next_stops.push_back(next_stop);
286                 }
287             }
288         }
289     }
290     return coords;
291 }
292 
293 void
sp_gradient_context_add_stops_between_selected_stops(GradientTool * rc)294 sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc)
295 {
296     SPDocument *doc = nullptr;
297     GrDrag *drag = rc->_grdrag;
298 
299     std::vector<SPStop *> these_stops;
300     std::vector<SPStop *> next_stops;
301 
302     std::vector<Geom::Point> coords = sp_gradient_context_get_stop_intervals (drag, these_stops, next_stops);
303 
304     if (these_stops.empty() && drag->numSelected() == 1) {
305         // if a single stop is selected, add between that stop and the next one
306         GrDragger *dragger = *(drag->selected.begin());
307         for (auto d : dragger->draggables) {
308             if (d->point_type == POINT_RG_FOCUS) {
309                 /*
310                  *  There are 2 draggables at the center (start) of a radial gradient
311                  *  To avoid creating 2 separate stops, ignore this draggable point type
312                  */
313                 continue;
314             }
315             SPGradient *gradient = getGradient(d->item, d->fill_or_stroke);
316             SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
317             SPStop *this_stop = sp_get_stop_i (vector, d->point_i);
318             if (this_stop) {
319                 SPStop *next_stop = this_stop->getNextStop();
320                 if (next_stop) {
321                     these_stops.push_back(this_stop);
322                     next_stops.push_back(next_stop);
323                 }
324             }
325         }
326     }
327 
328     // now actually create the new stops
329     auto i = these_stops.rbegin();
330     auto j = next_stops.rbegin();
331     std::vector<SPStop *> new_stops;
332 
333     for (;i != these_stops.rend() && j != next_stops.rend(); ++i, ++j ) {
334         SPStop *this_stop = *i;
335         SPStop *next_stop = *j;
336         gfloat offset = 0.5*(this_stop->offset + next_stop->offset);
337         SPObject *parent = this_stop->parent;
338         if (SP_IS_GRADIENT (parent)) {
339             doc = parent->document;
340             SPStop *new_stop = sp_vector_add_stop (SP_GRADIENT (parent), this_stop, next_stop, offset);
341             new_stops.push_back(new_stop);
342             SP_GRADIENT(parent)->ensureVector();
343         }
344     }
345 
346     if (!these_stops.empty() && doc) {
347         DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Add gradient stop"));
348         drag->updateDraggers();
349         // so that it does not automatically update draggers in idle loop, as this would deselect
350         drag->local_change = true;
351 
352         // select the newly created stops
353         for (auto i:new_stops) {
354             drag->selectByStop(i);
355         }
356     }
357 }
358 
sqr(double x)359 static double sqr(double x) {return x*x;}
360 
361 /**
362  * Remove unnecessary stops in the adjacent currently selected stops
363  *
364  * For selected stops that are adjacent to each other, remove
365  * stops that don't change the gradient visually, within a range of tolerance.
366  *
367  * @param rc GradientTool used to extract selected stops
368  * @param tolerance maximum difference between stop and expected color at that position
369  */
370 static void
sp_gradient_simplify(GradientTool * rc,double tolerance)371 sp_gradient_simplify(GradientTool *rc, double tolerance)
372 {
373     SPDocument *doc = nullptr;
374     GrDrag *drag = rc->_grdrag;
375 
376     std::vector<SPStop *> these_stops;
377     std::vector<SPStop *> next_stops;
378 
379     std::vector<Geom::Point> coords = sp_gradient_context_get_stop_intervals (drag, these_stops, next_stops);
380 
381     std::set<SPStop *> todel;
382 
383     auto i = these_stops.begin();
384     auto j = next_stops.begin();
385     for (; i != these_stops.end() && j != next_stops.end(); ++i, ++j) {
386         SPStop *stop0 = *i;
387         SPStop *stop1 = *j;
388 
389         // find the next adjacent stop if it exists and is in selection
390         auto i1 = std::find(these_stops.begin(), these_stops.end(), stop1);
391         if (i1 != these_stops.end()) {
392             if (next_stops.size()>(i1-these_stops.begin())) {
393                 SPStop *stop2 = *(next_stops.begin() + (i1-these_stops.begin()));
394 
395                 if (todel.find(stop0)!=todel.end() || todel.find(stop2) != todel.end())
396                     continue;
397 
398                 // compare color of stop1 to the average color of stop0 and stop2
399                 guint32 const c0 = stop0->get_rgba32();
400                 guint32 const c2 = stop2->get_rgba32();
401                 guint32 const c1r = stop1->get_rgba32();
402                 guint32 c1 = average_color (c0, c2,
403                        (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset));
404 
405                 double diff =
406                     sqr(SP_RGBA32_R_F(c1) - SP_RGBA32_R_F(c1r)) +
407                     sqr(SP_RGBA32_G_F(c1) - SP_RGBA32_G_F(c1r)) +
408                     sqr(SP_RGBA32_B_F(c1) - SP_RGBA32_B_F(c1r)) +
409                     sqr(SP_RGBA32_A_F(c1) - SP_RGBA32_A_F(c1r));
410 
411                 if (diff < tolerance)
412                     todel.insert(stop1);
413             }
414         }
415     }
416 
417     for (auto stop : todel) {
418         doc = stop->document;
419         Inkscape::XML::Node * parent = stop->getRepr()->parent();
420         parent->removeChild( stop->getRepr() );
421     }
422 
423     if (!todel.empty()) {
424         DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Simplify gradient"));
425         drag->local_change = true;
426         drag->updateDraggers();
427         drag->selectByCoords(coords);
428     }
429 }
430 
431 
432 static void
sp_gradient_context_add_stop_near_point(GradientTool * rc,SPItem * item,Geom::Point mouse_p,guint32)433 sp_gradient_context_add_stop_near_point (GradientTool *rc, SPItem *item,  Geom::Point mouse_p, guint32 /*etime*/)
434 {
435     // item is the selected item. mouse_p the location in doc coordinates of where to add the stop
436 
437     const SPDesktop *desktop = rc->getDesktop();
438 
439     double tolerance = (double) rc->tolerance;
440 
441     SPStop *newstop = rc->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom());
442 
443     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
444                        _("Add gradient stop"));
445 
446     rc->get_drag()->updateDraggers();
447     rc->get_drag()->local_change = true;
448     rc->get_drag()->selectByStop(newstop);
449 }
450 
root_handler(GdkEvent * event)451 bool GradientTool::root_handler(GdkEvent* event) {
452     static bool dragging;
453 
454     Inkscape::Selection *selection = desktop->getSelection();
455 
456     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
457     tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
458 
459     GrDrag *drag = this->_grdrag;
460     g_assert (drag);
461 
462     gint ret = FALSE;
463 
464     switch (event->type) {
465     case GDK_2BUTTON_PRESS:
466         if ( event->button.button == 1 ) {
467 
468             SPItem* item = sp_gradient_context_is_over_curve(this, Geom::Point(event->motion.x, event->motion.y));
469             if (item) {
470                 // we take the first item in selection, because with doubleclick, the first click
471                 // always resets selection to the single object under cursor
472                 sp_gradient_context_add_stop_near_point(this, SP_ITEM(selection->items().front()), mousepoint_doc, event->button.time);
473             } else {
474             	auto items= selection->items();
475                 for (auto i = items.begin();i!=items.end();++i) {
476                     SPItem *item = *i;
477                     SPGradientType new_type = (SPGradientType) prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR);
478                     Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
479 
480                     SPGradient *vector = sp_gradient_vector_for_object(desktop->getDocument(), desktop, item, fsmode);
481 
482                     SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode);
483                     sp_gradient_reset_to_userspace(priv, item);
484                 }
485                 desktop->redrawDesktop();;
486                 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
487                                    _("Create default gradient"));
488             }
489             ret = TRUE;
490         }
491         break;
492 
493     case GDK_BUTTON_PRESS:
494         if ( event->button.button == 1 ) {
495             Geom::Point button_w(event->button.x, event->button.y);
496 
497             // save drag origin
498             this->xp = (gint) button_w[Geom::X];
499             this->yp = (gint) button_w[Geom::Y];
500             this->within_tolerance = true;
501 
502             dragging = true;
503 
504             Geom::Point button_dt = desktop->w2d(button_w);
505             if (event->button.state & GDK_SHIFT_MASK) {
506                 Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
507             } else {
508                 // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
509                 // enable Ctrl+doubleclick of exactly the selected item(s)
510                 if (!(event->button.state & GDK_CONTROL_MASK)) {
511                     this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);
512                 }
513 
514                 if (!selection->isEmpty()) {
515                     SnapManager &m = desktop->namedview->snap_manager;
516                     m.setup(desktop);
517                     m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
518                     m.unSetup();
519                 }
520 
521                 this->origin = button_dt;
522             }
523 
524             ret = TRUE;
525         }
526         break;
527 
528     case GDK_MOTION_NOTIFY:
529         if (dragging && ( event->motion.state & GDK_BUTTON1_MASK )) {
530             if ( this->within_tolerance
531                  && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
532                  && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
533                 break; // do not drag if we're within tolerance from origin
534             }
535             // Once the user has moved farther than tolerance from the original location
536             // (indicating they intend to draw, not click), then always process the
537             // motion notify coordinates as given (no snapping back to origin)
538             this->within_tolerance = false;
539 
540             Geom::Point const motion_w(event->motion.x,
541                                      event->motion.y);
542             Geom::Point const motion_dt = this->desktop->w2d(motion_w);
543 
544             if (Inkscape::Rubberband::get(desktop)->is_started()) {
545                 Inkscape::Rubberband::get(desktop)->move(motion_dt);
546                 this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
547             } else {
548                 sp_gradient_drag(*this, motion_dt, event->motion.state, event->motion.time);
549             }
550 
551             gobble_motion_events(GDK_BUTTON1_MASK);
552 
553             ret = TRUE;
554         } else {
555             if (!drag->mouseOver() && !selection->isEmpty()) {
556                 SnapManager &m = desktop->namedview->snap_manager;
557                 m.setup(desktop);
558 
559                 Geom::Point const motion_w(event->motion.x, event->motion.y);
560                 Geom::Point const motion_dt = this->desktop->w2d(motion_w);
561 
562                 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE));
563                 m.unSetup();
564             }
565 
566             SPItem *item = sp_gradient_context_is_over_curve(this, Geom::Point(event->motion.x, event->motion.y));
567 
568             if (this->cursor_addnode && !item) {
569                 cursor_filename = "gradient.svg";
570                 this->sp_event_context_update_cursor();
571                 this->cursor_addnode = false;
572             } else if (!this->cursor_addnode && item) {
573                 cursor_filename = "gradient-add.svg";
574                 this->sp_event_context_update_cursor();
575                 this->cursor_addnode = true;
576             }
577         }
578         break;
579 
580     case GDK_BUTTON_RELEASE:
581         this->xp = this->yp = 0;
582 
583         if ( event->button.button == 1 ) {
584 
585             SPItem *item = sp_gradient_context_is_over_curve(this, Geom::Point(event->motion.x, event->motion.y));
586 
587             if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) {
588                 if (item) {
589                     sp_gradient_context_add_stop_near_point(this, item, this->mousepoint_doc, 0);
590                     ret = TRUE;
591                 }
592             } else {
593                 dragging = false;
594 
595                 // unless clicked with Ctrl (to enable Ctrl+doubleclick).
596                 if (event->button.state & GDK_CONTROL_MASK) {
597                     ret = TRUE;
598                     break;
599                 }
600 
601                 if (!this->within_tolerance) {
602                     // we've been dragging, either do nothing (grdrag handles that),
603                     // or rubberband-select if we have rubberband
604                     Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
605 
606                     if (r->is_started() && !this->within_tolerance) {
607                         // this was a rubberband drag
608                         if (r->getMode() == RUBBERBAND_MODE_RECT) {
609                             Geom::OptRect const b = r->getRectangle();
610                             drag->selectRect(*b);
611                         }
612                     }
613                 } else if (this->item_to_select) {
614                     if (item) {
615                         // Clicked on an existing gradient line, don't change selection. This stops
616                         // possible change in selection during a double click with overlapping objects
617                     } else {
618                         // no dragging, select clicked item if any
619                         if (event->button.state & GDK_SHIFT_MASK) {
620                             selection->toggle(this->item_to_select);
621                         } else {
622                             drag->deselectAll();
623                             selection->set(this->item_to_select);
624                         }
625                     }
626                 } else {
627                     // click in an empty space; do the same as Esc
628                     if (!drag->selected.empty()) {
629                         drag->deselectAll();
630                     } else {
631                         selection->clear();
632                     }
633                 }
634 
635                 this->item_to_select = nullptr;
636                 ret = TRUE;
637             }
638 
639             Inkscape::Rubberband::get(desktop)->stop();
640         }
641         break;
642 
643     case GDK_KEY_PRESS:
644         switch (get_latin_keyval (&event->key)) {
645         case GDK_KEY_Alt_L:
646         case GDK_KEY_Alt_R:
647         case GDK_KEY_Control_L:
648         case GDK_KEY_Control_R:
649         case GDK_KEY_Shift_L:
650         case GDK_KEY_Shift_R:
651         case GDK_KEY_Meta_L:  // Meta is when you press Shift+Alt (at least on my machine)
652         case GDK_KEY_Meta_R:
653             sp_event_show_modifier_tip (this->defaultMessageContext(), event,
654                                         _("<b>Ctrl</b>: snap gradient angle"),
655                                         _("<b>Shift</b>: draw gradient around the starting point"),
656                                         nullptr);
657             break;
658 
659         case GDK_KEY_x:
660         case GDK_KEY_X:
661             if (MOD__ALT_ONLY(event)) {
662                 desktop->setToolboxFocusTo ("altx-grad");
663                 ret = TRUE;
664             }
665             break;
666 
667         case GDK_KEY_A:
668         case GDK_KEY_a:
669             if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) {
670                 drag->selectAll();
671                 ret = TRUE;
672             }
673             break;
674 
675         case GDK_KEY_L:
676         case GDK_KEY_l:
677             if (MOD__CTRL_ONLY(event) && drag->isNonEmpty() && drag->hasSelection()) {
678                 sp_gradient_simplify(this, 1e-4);
679                 ret = TRUE;
680             }
681             break;
682 
683         case GDK_KEY_Escape:
684             if (!drag->selected.empty()) {
685                 drag->deselectAll();
686             } else {
687                 Inkscape::SelectionHelper::selectNone(desktop);
688             }
689             ret = TRUE;
690             //TODO: make dragging escapable by Esc
691             break;
692 
693         case GDK_KEY_r:
694         case GDK_KEY_R:
695             if (MOD__SHIFT_ONLY(event)) {
696                 sp_gradient_reverse_selected_gradients(desktop);
697                 ret = TRUE;
698             }
699             break;
700 
701         case GDK_KEY_Insert:
702         case GDK_KEY_KP_Insert:
703             // with any modifiers:
704             sp_gradient_context_add_stops_between_selected_stops (this);
705             ret = TRUE;
706             break;
707 
708         case GDK_KEY_i:
709         case GDK_KEY_I:
710             if (MOD__SHIFT_ONLY(event)) {
711                 // Shift+I - insert stops (alternate keybinding for keyboards
712                 //           that don't have the Insert key)
713                 sp_gradient_context_add_stops_between_selected_stops (this);
714                 ret = TRUE;
715             }
716             break;
717 
718         case GDK_KEY_Delete:
719         case GDK_KEY_KP_Delete:
720         case GDK_KEY_BackSpace:
721             ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
722             break;
723 
724         default:
725             ret = drag->key_press_handler(event);
726             break;
727         }
728         break;
729 
730     case GDK_KEY_RELEASE:
731         switch (get_latin_keyval (&event->key)) {
732         case GDK_KEY_Alt_L:
733         case GDK_KEY_Alt_R:
734         case GDK_KEY_Control_L:
735         case GDK_KEY_Control_R:
736         case GDK_KEY_Shift_L:
737         case GDK_KEY_Shift_R:
738         case GDK_KEY_Meta_L:  // Meta is when you press Shift+Alt
739         case GDK_KEY_Meta_R:
740             this->defaultMessageContext()->clear();
741             break;
742 
743         default:
744             break;
745         }
746         break;
747 
748     default:
749         break;
750     }
751 
752     if (!ret) {
753     	ret = ToolBase::root_handler(event);
754     }
755 
756     return ret;
757 }
758 
759 // Creates a new linear or radial gradient.
sp_gradient_drag(GradientTool & rc,Geom::Point const pt,guint,guint32 etime)760 static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint /*state*/, guint32 etime)
761 {
762     SPDesktop *desktop = rc.getDesktop();
763     Inkscape::Selection *selection = desktop->getSelection();
764     SPDocument *document = desktop->getDocument();
765 
766     if (!selection->isEmpty()) {
767         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
768         int type = prefs->getInt("/tools/gradient/newgradient", 1);
769         Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
770 
771         SPGradient *vector;
772         if (rc.item_to_select) {
773             // pick color from the object where drag started
774             vector = sp_gradient_vector_for_object(document, desktop, rc.item_to_select, fill_or_stroke);
775         } else {
776             // Starting from empty space:
777             // Sort items so that the topmost comes last
778         	std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
779             sort(items.begin(),items.end(),sp_item_repr_compare_position_bool);
780             // take topmost
781             vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(items.back()), fill_or_stroke);
782         }
783 
784         // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
785         SPCSSAttr *css = sp_repr_css_attr_new();
786         sp_repr_css_set_property(css, "fill-opacity", "1.0");
787 
788         auto itemlist = selection->items();
789         for (auto i = itemlist.begin();i!=itemlist.end();++i) {
790 
791             //FIXME: see above
792             sp_repr_css_change_recursive((*i)->getRepr(), css, "style");
793 
794             sp_item_set_gradient(*i, vector, (SPGradientType) type, fill_or_stroke);
795 
796             if (type == SP_GRADIENT_TYPE_LINEAR) {
797                 sp_item_gradient_set_coords (*i, POINT_LG_BEGIN, 0, rc.origin, fill_or_stroke, true, false);
798                 sp_item_gradient_set_coords (*i, POINT_LG_END, 0, pt, fill_or_stroke, true, false);
799             } else if (type == SP_GRADIENT_TYPE_RADIAL) {
800                 sp_item_gradient_set_coords (*i, POINT_RG_CENTER, 0, rc.origin, fill_or_stroke, true, false);
801                 sp_item_gradient_set_coords (*i, POINT_RG_R1, 0, pt, fill_or_stroke, true, false);
802             }
803             (*i)->requestModified(SP_OBJECT_MODIFIED_FLAG);
804         }
805         if (rc._grdrag) {
806             rc._grdrag->updateDraggers();
807             // prevent regenerating draggers by selection modified signal, which sometimes
808             // comes too late and thus destroys the knot which we will now grab:
809             rc._grdrag->local_change = true;
810             // give the grab out-of-bounds values of xp/yp because we're already dragging
811             // and therefore are already out of tolerance
812             rc._grdrag->grabKnot (selection->items().front(),
813                                    type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1,
814                                    -1, // ignore number (though it is always 1)
815                                    fill_or_stroke, 99999, 99999, etime);
816         }
817         // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released
818 
819         // status text; we do not track coords because this branch is run once, not all the time
820         // during drag
821         int n_objects = (int) boost::distance(selection->items());
822         rc.message_context->setF(Inkscape::NORMAL_MESSAGE,
823                                   ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle",
824                                            "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects),
825                                   n_objects);
826     } else {
827         desktop->getMessageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient."));
828     }
829 }
830 
831 }
832 }
833 }
834 
835 
836 /*
837   Local Variables:
838   mode:c++
839   c-file-style:"stroustrup"
840   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
841   indent-tabs-mode:nil
842   fill-column:99
843   End:
844 */
845 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
846