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