Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
gradient-tool.cpp
Go to the documentation of this file.
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 <unordered_set>
17#include <glibmm/i18n.h>
18#include <gdk/gdkkeysyms.h>
19
20#include "desktop.h"
21#include "document-undo.h"
22#include "gradient-chemistry.h"
23#include "gradient-drag.h"
24#include "message-context.h"
25#include "message-stack.h"
26#include "rubberband.h"
27#include "selection-chemistry.h"
28#include "selection.h"
29#include "snap.h"
30
31#include "object/sp-namedview.h"
32#include "object/sp-stop.h"
33
35
36#include "ui/icon-names.h"
39
41
42namespace Inkscape::UI::Tools {
43
45 : ToolBase(desktop, "/tools/gradient", "gradient.svg")
46{
47 // TODO: This value is overwritten in the root handler
48 tolerance = 6;
49
50 auto prefs = Preferences::get();
51
52 if (prefs->getBool("/tools/gradient/selcue", true)) {
54 }
55
57
58 auto selection = desktop->getSelection();
59 selcon = selection->connectChanged([this] (auto) { selection_changed(); });
60
63 if (stop) {
64 // Sync stop selection.
65 _grdrag->selectByStop(stop, false, true);
66 }
67 });
68
70}
71
76
77// This must match GrPointType enum sp-gradient.h
78// We should move this to a shared header (can't simply move to gradient.h since that would require
79// including <glibmm/i18n.h> which messes up "N_" in extensions... argh!).
80static char const *gr_handle_descr[] = {
81 N_("Linear gradient <b>start</b>"), // POINT_LG_BEGIN
82 N_("Linear gradient <b>end</b>"),
83 N_("Linear gradient <b>mid stop</b>"),
84 N_("Radial gradient <b>center</b>"),
85 N_("Radial gradient <b>radius</b>"),
86 N_("Radial gradient <b>radius</b>"),
87 N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
88 N_("Radial gradient <b>mid stop</b>"),
89 N_("Radial gradient <b>mid stop</b>"),
90 N_("Mesh gradient <b>corner</b>"),
91 N_("Mesh gradient <b>handle</b>"),
92 N_("Mesh gradient <b>tensor</b>")
93};
94
96{
97 auto const selection = _desktop->getSelection();
98 if (!selection) {
99 return;
100 }
101 unsigned const n_obj = boost::distance(selection->items());
102
103 if (!_grdrag->isNonEmpty() || selection->isEmpty()) {
104 return;
105 }
106 unsigned const n_tot = _grdrag->numDraggers();
107 unsigned const n_sel = _grdrag->numSelected();
108
109 // Note: The use of ngettext in the following code is intentional even if the English singular form would never be used.
110 if (n_sel == 1) {
112 auto const message = Glib::ustring::format(
113 // TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message
114 _("%s selected"),
115 // TRANSLATORS: Mind the space in front. This is part of a compound message
116 ngettext(" out of %d gradient handle"," out of %d gradient handles", n_tot),
117 ngettext(" on %d selected object"," on %d selected objects", n_obj));
119 message.c_str(), _(gr_handle_descr[_grdrag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj);
120 } else {
121 auto const message = Glib::ustring::format(
122 // TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count)
123 ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
124 "One handle merging %d stops (drag with <b>Shift</b> to separate) selected", _grdrag->singleSelectedDraggerNumDraggables()),
125 ngettext(" out of %d gradient handle"," out of %d gradient handles", n_tot),
126 ngettext(" on %d selected object"," on %d selected objects", n_obj));
127 message_context->setF(NORMAL_MESSAGE, message.c_str(), _grdrag->singleSelectedDraggerNumDraggables(), n_tot, n_obj);
128 }
129 } else if (n_sel > 1) {
130 // TRANSLATORS: The plural refers to number of selected gradient handles. This is part of a compound message (part two indicates selected object count)
131 auto const message = Glib::ustring::format(
132 ngettext("<b>%d</b> gradient handle selected out of %d","<b>%d</b> gradient handles selected out of %d",n_sel),
133 // TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message
134 ngettext(" on %d selected object"," on %d selected objects", n_obj));
135 message_context->setF(NORMAL_MESSAGE, message.c_str(), n_sel, n_tot, n_obj);
136 } else if (n_sel == 0) {
138 // TRANSLATORS: The plural refers to number of selected objects
139 ngettext("<b>No</b> gradient handles selected out of %d on %d selected object",
140 "<b>No</b> gradient handles selected out of %d on %d selected objects", n_obj), n_tot, n_obj);
141 }
142}
143
145{
146 g_assert(_grdrag);
147 auto d = _grdrag->select_next();
148 _desktop->scroll_to_point(d->point);
149}
150
152{
153 g_assert(_grdrag);
154 auto d = _grdrag->select_prev();
155 _desktop->scroll_to_point(d->point);
156}
157
159{
160 // Translate mouse point into proper coord system: needed later.
161 mousepoint_doc = _desktop->w2d(event_p);
162
163 for (auto &it : _grdrag->item_curves) {
164 if (it.curve->contains(event_p, tolerance)) {
165 return it.item;
166 }
167 }
168
169 return nullptr;
170}
171
172struct StopIntervals
173{
174 std::vector<Geom::Point> coords;
175 std::vector<SPStop*> these_stops;
176 std::vector<SPStop*> next_stops;
177};
178
179static auto get_stop_intervals(GrDrag *drag)
180{
181 StopIntervals result;
182
183 // for all selected draggers
184 for (auto const dragger : drag->selected) {
185 // remember the coord of the dragger to reselect it later
186 result.coords.emplace_back(dragger->point);
187 // for all draggables of dragger
188 for (auto const d : dragger->draggables) {
189 // find the gradient
190 auto const gradient = getGradient(d->item, d->fill_or_stroke);
191 auto const vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
192
193 // these draggable types cannot have a next draggable to insert a stop between them
194 if (d->point_type == POINT_LG_END ||
195 d->point_type == POINT_RG_FOCUS ||
196 d->point_type == POINT_RG_R1 ||
197 d->point_type == POINT_RG_R2)
198 {
199 continue;
200 }
201
202 // from draggables to stops
203 auto const this_stop = sp_get_stop_i(vector, d->point_i);
204 auto const next_stop = this_stop->getNextStop();
205 auto const last_stop = sp_last_stop(vector);
206
207 auto const fs = d->fill_or_stroke;
208 auto const item = d->item;
209 auto const type = d->point_type;
210 auto const p_i = d->point_i;
211
212 // if there's a next stop,
213 if (next_stop) {
214 GrDragger *dnext = nullptr;
215 // find its dragger
216 // (complex because it may have different types, and because in radial,
217 // more than one dragger may correspond to a stop, so we must distinguish)
218 if (type == POINT_LG_BEGIN || type == POINT_LG_MID) {
219 if (next_stop == last_stop) {
220 dnext = drag->getDraggerFor(item, POINT_LG_END, p_i+1, fs);
221 } else {
222 dnext = drag->getDraggerFor(item, POINT_LG_MID, p_i+1, fs);
223 }
224 } else { // radial
225 if (type == POINT_RG_CENTER || type == POINT_RG_MID1) {
226 if (next_stop == last_stop) {
227 dnext = drag->getDraggerFor(item, POINT_RG_R1, p_i+1, fs);
228 } else {
229 dnext = drag->getDraggerFor(item, POINT_RG_MID1, p_i+1, fs);
230 }
231 }
232 if ((type == POINT_RG_MID2) ||
233 (type == POINT_RG_CENTER && dnext && !dnext->isSelected()))
234 {
235 if (next_stop == last_stop) {
236 dnext = drag->getDraggerFor(item, POINT_RG_R2, p_i+1, fs);
237 } else {
238 dnext = drag->getDraggerFor(item, POINT_RG_MID2, p_i+1, fs);
239 }
240 }
241 }
242
243 // if both adjacent draggers selected,
244 if ((std::find(result.these_stops.begin(), result.these_stops.end(), this_stop) == result.these_stops.end()) && dnext && dnext->isSelected()) {
245 // remember the coords of the future dragger to select it
246 result.coords.emplace_back((dragger->point + dnext->point) / 2);
247
248 // do not insert a stop now, it will confuse the loop;
249 // just remember the stops
250 result.these_stops.emplace_back(this_stop);
251 result.next_stops.emplace_back(next_stop);
252 }
253 }
254 }
255 }
256
257 return result;
258}
259
261{
262 if (_grdrag->hasSelection()) {
263 auto dragger = *_grdrag->selected.begin();
264 auto draggable = dragger->draggables[0];
265 auto gradient = getGradient(draggable->item, draggable->fill_or_stroke);
266 auto vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
267
268 // Treat single stop gradients separately.
269 if (vector->getStopCount() == 1) {
270 auto newstop = sp_gradient_add_stop(vector, vector->getFirstStop());
271 gradient->ensureVector();
273 _grdrag->local_change = true;
274 _grdrag->selectByStop(newstop);
275 DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient"));
276 return;
277 }
278 }
279
280 auto ret = get_stop_intervals(_grdrag);
281
282 if (ret.these_stops.empty() && _grdrag->numSelected() == 1) {
283 // if a single stop is selected, add between that stop and the next one
284 auto dragger = *_grdrag->selected.begin();
285 for (auto d : dragger->draggables) {
286 if (d->point_type == POINT_RG_FOCUS) {
287 // There are 2 draggables at the center (start) of a radial gradient
288 // To avoid creating 2 separate stops, ignore this draggable point type
289 continue;
290 }
291
292 // Mark the second-to-last stop as the current stop if the last gradient point is
293 // selected to insert a stop.
294 auto const point_i =
295 d->point_type == POINT_LG_END || d->point_type == POINT_RG_R1 || d->point_type == POINT_RG_R2
296 ? d->point_i - 1
297 : d->point_i;
298 auto gradient = getGradient(d->item, d->fill_or_stroke);
299 auto vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
300 if (auto this_stop = sp_get_stop_i(vector, point_i)) {
301 if (auto next_stop = this_stop->getNextStop()) {
302 ret.these_stops.emplace_back(this_stop);
303 ret.next_stops.emplace_back(next_stop);
304 }
305 }
306 }
307 }
308
309 // now actually create the new stops
310 auto i = ret.these_stops.rbegin();
311 auto j = ret.next_stops.rbegin();
312 std::vector<SPStop *> new_stops;
313 SPDocument *doc = nullptr;
314
315 for (; i != ret.these_stops.rend() && j != ret.next_stops.rend(); ++i, ++j) {
316 SPStop *this_stop = *i;
317 SPStop *next_stop = *j;
318 float offset = (this_stop->offset + next_stop->offset) / 2;
319 if (auto grad = cast<SPGradient>(this_stop->parent)) {
320 doc = grad->document;
321 auto new_stop = sp_vector_add_stop(grad, this_stop, next_stop, offset);
322 new_stops.emplace_back(new_stop);
323 grad->ensureVector();
324 }
325 }
326
327 if (!ret.these_stops.empty() && doc) {
328 DocumentUndo::done(doc, _("Add gradient stop"), INKSCAPE_ICON("color-gradient"));
330 // so that it does not automatically update draggers in idle loop, as this would deselect
331 _grdrag->local_change = true;
332
333 // select the newly created stops
334 for (auto s : new_stops) {
336 }
337 }
338}
339
348void GradientTool::simplify(double tolerance)
349{
350 SPDocument *doc = nullptr;
352
353 auto const ret = get_stop_intervals(drag);
354
355 std::unordered_set<SPStop *> todel;
356
357 auto i = ret.these_stops.begin();
358 auto j = ret.next_stops.begin();
359 for (; i != ret.these_stops.end() && j != ret.next_stops.end(); ++i, ++j) {
360 SPStop *stop0 = *i;
361 SPStop *stop1 = *j;
362
363 // find the next adjacent stop if it exists and is in selection
364 auto i1 = std::find(ret.these_stops.begin(), ret.these_stops.end(), stop1);
365 if (i1 != ret.these_stops.end()) {
366 if (ret.next_stops.size() > i1 - ret.these_stops.begin()) {
367 SPStop *stop2 = *(ret.next_stops.begin() + (i1 - ret.these_stops.begin()));
368
369 if (todel.find(stop0) != todel.end() || todel.find(stop2) != todel.end()) {
370 continue;
371 }
372
373 // compare color of stop1 to the average color of stop0 and stop2
374 auto coord = (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset);
375 auto avg = stop0->getColor().averaged(stop2->getColor(), coord);
376 if (avg.difference(stop1->getColor()) < tolerance) {
377 todel.emplace(stop1);
378 }
379 }
380 }
381 }
382
383 for (auto stop : todel) {
384 doc = stop->document;
385 auto parent = stop->getRepr()->parent();
386 parent->removeChild(stop->getRepr());
387 }
388
389 if (!todel.empty()) {
390 DocumentUndo::done(doc, _("Simplify gradient"), INKSCAPE_ICON("color-gradient"));
391 drag->local_change = true;
392 drag->updateDraggers();
393 drag->selectByCoords(ret.coords);
394 }
395}
396
398{
399 // item is the selected item. mouse_p the location in doc coordinates of where to add the stop
400 auto newstop = get_drag()->addStopNearPoint(item, mouse_p, tolerance / _desktop->current_zoom());
401
402 DocumentUndo::done(_desktop->getDocument(), _("Add gradient stop"), INKSCAPE_ICON("color-gradient"));
403
405 get_drag()->local_change = true;
406 get_drag()->selectByStop(newstop);
407}
408
410{
411 auto selection = _desktop->getSelection();
412
413 auto prefs = Preferences::get();
414 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
415
416 bool ret = false;
417
418 inspect_event(event,
419 [&] (ButtonPressEvent const &event) {
420 if (event.button != 1) {
421 return;
422 }
423
424 if (event.num_press == 2) {
425
426 if (is_over_curve(event.pos)) {
427 // we take the first item in selection, because with doubleclick, the first click
428 // always resets selection to the single object under cursor
429 add_stop_near_point(selection->items().front(), mousepoint_doc);
430 } else {
431 for (auto item : selection->items()) {
432 auto const new_type = static_cast<SPGradientType>(prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR));
433 auto const fsmode = prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0 ? FOR_FILL : FOR_STROKE;
434
435 SPGradient *vector = sp_gradient_vector_for_object(_desktop->getDocument(), _desktop, item, fsmode);
436
437 SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode);
438 sp_gradient_reset_to_userspace(priv, item);
439 }
440 DocumentUndo::done(_desktop->getDocument(), _("Create default gradient"), INKSCAPE_ICON("color-gradient"));
441 }
442 ret = true;
443
444 } else if (event.num_press == 1) {
445
446 saveDragOrigin(event.pos);
447 dragging = true;
448
449 auto button_dt = _desktop->w2d(event.pos);
450 if (event.modifiers & GDK_SHIFT_MASK && !(event.modifiers & GDK_CONTROL_MASK)) {
451 auto rubberband = Rubberband::get(_desktop);
452 rubberband->start(_desktop, button_dt);
453 } else {
454 // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
455 // enable Ctrl+doubleclick of exactly the selected item(s)
456 if (!(event.modifiers & GDK_CONTROL_MASK)) {
457 item_to_select = sp_event_context_find_item(_desktop, event.pos, event.modifiers & GDK_ALT_MASK, true);
458 }
459
460 if (!selection->isEmpty()) {
461 auto &m = _desktop->getNamedView()->snap_manager;
462 m.setup(_desktop);
463 m.freeSnapReturnByRef(button_dt, SNAPSOURCE_NODE_HANDLE);
464 m.unSetup();
465 }
466
467 origin = button_dt;
468 }
469 ret = true;
470 }
471 },
472
473 [&] (MotionEvent const &event) {
474 if (dragging && (event.modifiers & GDK_BUTTON1_MASK)) {
475 if (!checkDragMoved(event.pos)) {
476 return;
477 }
478
479 auto const motion_dt = _desktop->w2d(event.pos);
480
481 if (Rubberband::get(_desktop)->isStarted()) {
482 Rubberband::get(_desktop)->move(motion_dt);
483 defaultMessageContext()->set(NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
484 } else {
485 drag(motion_dt, event.time);
486 }
487
488 gobble_motion_events(GDK_BUTTON1_MASK);
489
490 ret = true;
491 } else {
492 if (!_grdrag->mouseOver() && !selection->isEmpty()) {
493 auto &m = _desktop->getNamedView()->snap_manager;
494 m.setup(_desktop);
495
496 auto const motion_dt = _desktop->w2d(event.pos);
497
498 m.preSnap(SnapCandidatePoint(motion_dt, SNAPSOURCE_OTHER_HANDLE));
499 m.unSetup();
500 }
501
502 auto item = is_over_curve(event.pos);
503
504 if (cursor_addnode && !item) {
505 set_cursor("gradient.svg");
506 cursor_addnode = false;
507 } else if (!cursor_addnode && item) {
508 set_cursor("gradient-add.svg");
509 cursor_addnode = true;
510 }
511 }
512 },
513
514 [&] (ButtonReleaseEvent const &event) {
515 if (event.button != 1) {
516 return;
517 }
518
519 xyp = {};
520
521 auto item = is_over_curve(event.pos);
522
523 if ((event.modifiers & GDK_CONTROL_MASK) && (event.modifiers & GDK_ALT_MASK)) {
524 if (item) {
525 add_stop_near_point(item, mousepoint_doc);
526 ret = true;
527 }
528 } else {
529 dragging = false;
530
531 // unless clicked with Ctrl (to enable Ctrl+doubleclick).
532 if (event.modifiers & GDK_CONTROL_MASK && !(event.modifiers & GDK_SHIFT_MASK)) {
533 ret = true;
534 Rubberband::get(_desktop)->stop();
535 return;
536 }
537
538 if (!within_tolerance) {
539 // we've been dragging, either do nothing (grdrag handles that),
540 // or rubberband-select if we have rubberband
541 auto r = Rubberband::get(_desktop);
542
543 if (r->isStarted() && !within_tolerance) {
544 // this was a rubberband drag
545 if (r->getMode() == Rubberband::Mode::RECT) {
546 _grdrag->selectRect(*r->getRectangle());
547 }
548 }
549 } else if (item_to_select) {
550 if (item) {
551 // Clicked on an existing gradient line, don't change selection. This stops
552 // possible change in selection during a double click with overlapping objects
553 } else {
554 // no dragging, select clicked item if any
555 if (event.modifiers & GDK_SHIFT_MASK) {
556 selection->toggle(item_to_select);
557 } else {
558 _grdrag->deselectAll();
559 selection->set(item_to_select);
560 }
561 }
562 } else {
563 // click in an empty space; do the same as Esc
564 if (!_grdrag->selected.empty()) {
565 _grdrag->deselectAll();
566 } else {
567 selection->clear();
568 }
569 }
570
571 item_to_select = nullptr;
572 ret = true;
573 }
574
575 Rubberband::get(_desktop)->stop();
576 },
577
578 [&] (KeyPressEvent const &event) {
579 switch (get_latin_keyval(event)) {
580 case GDK_KEY_Alt_L:
581 case GDK_KEY_Alt_R:
582 case GDK_KEY_Control_L:
583 case GDK_KEY_Control_R:
584 case GDK_KEY_Shift_L:
585 case GDK_KEY_Shift_R:
586 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
587 case GDK_KEY_Meta_R:
588 sp_event_show_modifier_tip(defaultMessageContext(), event,
589 _("<b>Ctrl</b>: snap gradient angle"),
590 _("<b>Shift</b>: draw gradient around the starting point"),
591 nullptr);
592 break;
593
594 case GDK_KEY_x:
595 case GDK_KEY_X:
596 if (mod_alt_only(event)) {
597 _desktop->setToolboxFocusTo("altx-grad");
598 ret = true;
599 }
600 break;
601
602 case GDK_KEY_A:
603 case GDK_KEY_a:
604 if (mod_ctrl_only(event) && _grdrag->isNonEmpty()) {
605 _grdrag->selectAll();
606 ret = true;
607 }
608 break;
609
610 case GDK_KEY_L:
611 case GDK_KEY_l:
612 if (mod_ctrl_only(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
613 simplify(1e-4);
614 ret = true;
615 }
616 break;
617
618 case GDK_KEY_Escape:
619 if (!_grdrag->selected.empty()) {
620 _grdrag->deselectAll();
621 } else {
623 }
624 ret = true;
625 //TODO: make dragging escapable by Esc
626 break;
627
628 case GDK_KEY_r:
629 case GDK_KEY_R:
630 if (mod_shift_only(event)) {
632 ret = true;
633 }
634 break;
635
636 case GDK_KEY_Insert:
637 case GDK_KEY_KP_Insert:
638 // with any modifiers:
639 add_stops_between_selected_stops();
640 ret = true;
641 break;
642
643 case GDK_KEY_i:
644 case GDK_KEY_I:
645 if (mod_shift_only(event)) {
646 // Shift+I - insert stops (alternate keybinding for keyboards
647 // that don't have the Insert key)
648 add_stops_between_selected_stops();
649 ret = true;
650 }
651 break;
652
653 case GDK_KEY_Delete:
654 case GDK_KEY_KP_Delete:
655 case GDK_KEY_BackSpace:
656 ret = deleteSelectedDrag(mod_ctrl_only(event));
657 break;
658
659 case GDK_KEY_Tab:
660 if (hasGradientDrag()) {
661 select_next();
662 ret = true;
663 }
664 break;
665
666 case GDK_KEY_ISO_Left_Tab:
667 if (hasGradientDrag()) {
668 select_prev();
669 ret = true;
670 }
671 break;
672
673 default:
674 ret = _grdrag->key_press_handler(event);
675 break;
676 }
677 },
678
679 [&] (KeyReleaseEvent const &event) {
680 switch (get_latin_keyval(event)) {
681 case GDK_KEY_Alt_L:
682 case GDK_KEY_Alt_R:
683 case GDK_KEY_Control_L:
684 case GDK_KEY_Control_R:
685 case GDK_KEY_Shift_L:
686 case GDK_KEY_Shift_R:
687 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
688 case GDK_KEY_Meta_R:
689 defaultMessageContext()->clear();
690 break;
691
692 default:
693 break;
694 }
695 },
696
697 [&] (CanvasEvent const &event) {}
698 );
699
700 return ret || ToolBase::root_handler(event);
701}
702
703// Creates a new linear or radial gradient.
704void GradientTool::drag(Geom::Point const &pt, uint32_t etime)
705{
706 auto selection = _desktop->getSelection();
707 auto document = _desktop->getDocument();
708
709 if (!selection->isEmpty()) {
710 auto prefs = Preferences::get();
711 int type = prefs->getInt("/tools/gradient/newgradient", 1);
712 auto fill_or_stroke = prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0 ? FOR_FILL : FOR_STROKE;
713
714 SPGradient *vector;
715 if (item_to_select) {
716 // pick color from the object where drag started
717 vector = sp_gradient_vector_for_object(document, _desktop, item_to_select, fill_or_stroke);
718 } else {
719 // Starting from empty space:
720 // Sort items so that the topmost comes last
721 auto items = std::vector<SPItem*>(selection->items().begin(), selection->items().end());
722 std::sort(items.begin(), items.end(), sp_item_repr_compare_position_bool);
723 // take topmost
724 vector = sp_gradient_vector_for_object(document, _desktop, items.back(), fill_or_stroke);
725 }
726
727 // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
728 auto css = sp_repr_css_attr_new();
729 sp_repr_css_set_property(css, "fill-opacity", "1.0");
730
731 for (auto item : selection->items()) {
732
733 //FIXME: see above
735
736 sp_item_set_gradient(item, vector, static_cast<SPGradientType>(type), fill_or_stroke);
737
738 if (type == SP_GRADIENT_TYPE_LINEAR) {
739 sp_item_gradient_set_coords(item, POINT_LG_BEGIN, 0, origin, fill_or_stroke, true, false);
740 sp_item_gradient_set_coords (item, POINT_LG_END, 0, pt, fill_or_stroke, true, false);
741 } else if (type == SP_GRADIENT_TYPE_RADIAL) {
742 sp_item_gradient_set_coords(item, POINT_RG_CENTER, 0, origin, fill_or_stroke, true, false);
743 sp_item_gradient_set_coords (item, POINT_RG_R1, 0, pt, fill_or_stroke, true, false);
744 }
745 item->requestModified(SP_OBJECT_MODIFIED_FLAG);
746 }
747
749
750 if (_grdrag) {
751 _grdrag->updateDraggers();
752 // prevent regenerating draggers by selection modified signal, which sometimes
753 // comes too late and thus destroys the knot which we will now grab:
754 _grdrag->local_change = true;
755 // give the grab out-of-bounds values of xp/yp because we're already dragging
756 // and therefore are already out of tolerance
757 _grdrag->grabKnot (selection->items().front(),
759 -1, // ignore number (though it is always 1)
760 fill_or_stroke, 99999, 99999, etime);
761 }
762 // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released
763
764 // status text; we do not track coords because this branch is run once, not all the time
765 // during drag
766 int const n_objects = boost::distance(selection->items());
767 message_context->setF(NORMAL_MESSAGE,
768 ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle",
769 "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects),
770 n_objects);
771 } else {
772 _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient."));
773 }
774}
775
776} // namespace Inkscape::UI::Tools
777
778/*
779 Local Variables:
780 mode:c++
781 c-file-style:"stroustrup"
782 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
783 indent-tabs-mode:nil
784 fill-column:99
785 End:
786*/
787// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
Two-dimensional point that doubles as a vector.
Definition point.h:66
This is the root class of the gradient dragging machinery.
void updateDraggers()
Regenerates the draggers list from the current selection; is called when selection is changed or modi...
bool hasSelection()
guint singleSelectedDraggerSingleDraggableType()
GrDragger * select_prev()
Select the knot previous from the last selected one and deselect all other selected.
bool isNonEmpty()
void selectByStop(SPStop *stop, bool add_to_selection=true, bool override=true)
Select draggers by stop.
bool local_change
guint singleSelectedDraggerNumDraggables()
GrDragger * select_next()
Select the knot next to the last selected one and deselect all other selected.
SPStop * addStopNearPoint(SPItem *item, Geom::Point mouse_p, double tolerance)
guint numSelected()
std::set< GrDragger * > selected
std::vector< ItemCurve > item_curves
guint numDraggers()
GrDragger * getDraggerFor(GrDraggable *d)
Select the dragger which has the given draggable.
Color averaged(Color const &other, double pos=0.5) const
Return the average between this and another color.
Definition color.cpp:556
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static Preferences * get()
Access the singleton Preferences object.
static Rubberband * get(SPDesktop *desktop)
void move(Geom::Point const &p)
bool isStarted() const
Definition rubberband.h:53
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
sigc::scoped_connection selcon
void drag(Geom::Point const &pt, uint32_t etime)
SPItem * is_over_curve(Geom::Point const &event_p)
bool root_handler(CanvasEvent const &event) override
void add_stop_near_point(SPItem *item, Geom::Point const &mouse_p)
sigc::scoped_connection subselcon
void simplify(double tolerance)
Remove unnecessary stops in the adjacent currently selected stops.
Base class for Event processors.
Definition tool-base.h:107
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
void enableGrDrag(bool enable=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
sigc::connection connect_gradient_stop_selected(sigc::slot< void(SPStop *)> const &slot)
Definition desktop.cpp:1349
Inkscape::Selection * getSelection() const
Definition desktop.h:188
bool scroll_to_point(Geom::Point const &s_dt, double autoscrollspeed=0)
Scroll screen so as to keep point 'p' visible in window.
Definition desktop.cpp:894
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Typed SVG document implementation.
Definition document.h:101
Gradient.
Definition sp-gradient.h:86
Base class for visual SVG elements.
Definition sp-item.h:109
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
Gradient stop.
Definition sp-stop.h:31
float offset
Definition sp-stop.h:38
Inkscape::Colors::Color getColor() const
Definition sp-stop.cpp:130
std::shared_ptr< Css const > css
Css & result
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
SPStop * sp_get_stop_i(SPGradient *gradient, guint stop_i)
void sp_gradient_reverse_selected_gradients(SPDesktop *desktop)
SPStop * sp_last_stop(SPGradient *gradient)
void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint point_i, Geom::Point p_w, Inkscape::PaintTarget fill_or_stroke, bool write_repr, bool scale)
Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to p_w (...
SPStop * sp_vector_add_stop(SPGradient *vector, SPStop *prev_stop, SPStop *next_stop, gfloat offset)
SPStop * sp_gradient_add_stop(SPGradient *gradient, SPStop *current)
SPGradient * sp_gradient_get_forked_vector_if_necessary(SPGradient *gradient, bool force_vector)
Obtain the vector from the gradient.
SPGradient * getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Fetches either the fill or the stroke gradient from the given item.
SPGradient * sp_gradient_vector_for_object(SPDocument *const doc, SPDesktop *const desktop, SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop)
Return the preferred vector for o, made from (in order of preference) its current vector,...
SPGradient * sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke)
Sets item fill or stroke to the gradient of the specified type with given vector, creating new privat...
Gradient drawing and editing tool.
Macro for icon names used in Inkscape.
SPItem * item
double offset
Interface for locally managing a current status message.
Raw stack of active status messages.
void selectNone(SPDesktop *desktop)
static char const * gr_handle_descr[]
void sp_event_show_modifier_tip(MessageContext *message_context, KeyEvent const &event, char const *ctrl_tip, char const *shift_tip, char const *alt_tip)
Show tool context specific modifier tip.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
static auto get_stop_intervals(GrDrag *drag)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
bool mod_ctrl_only(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
@ NORMAL_MESSAGE
Definition message.h:26
@ WARNING_MESSAGE
Definition message.h:28
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
Definition repr-css.cpp:371
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
GList * items
@ POINT_LG_END
Definition sp-gradient.h:48
@ POINT_RG_R2
Definition sp-gradient.h:52
@ POINT_RG_MID2
Definition sp-gradient.h:55
@ POINT_RG_CENTER
Definition sp-gradient.h:50
@ POINT_LG_BEGIN
Definition sp-gradient.h:47
@ POINT_RG_MID1
Definition sp-gradient.h:54
@ POINT_RG_R1
Definition sp-gradient.h:51
@ POINT_LG_MID
Definition sp-gradient.h:49
@ POINT_RG_FOCUS
Definition sp-gradient.h:53
SPGradientType
Definition sp-gradient.h:33
@ SP_GRADIENT_TYPE_LINEAR
Definition sp-gradient.h:35
@ SP_GRADIENT_TYPE_RADIAL
Definition sp-gradient.h:36
bool sp_item_repr_compare_position_bool(SPObject const *first, SPObject const *second)
Definition sp-item.h:473
TODO: insert short description here.
This class holds together a visible on-canvas knot and a list of draggables that need to be moved whe...
Geom::Point point
A mouse button (left/right/middle) is pressed.
Abstract base class for events.
SPDesktop * desktop