16#include <unordered_set>
17#include <glibmm/i18n.h>
18#include <gdk/gdkkeysyms.h>
52 if (prefs->getBool(
"/tools/gradient/selcue",
true)) {
81 N_(
"Linear gradient <b>start</b>"),
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>"),
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>")
101 unsigned const n_obj = boost::distance(selection->items());
112 auto const message = Glib::ustring::format(
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));
121 auto const message = Glib::ustring::format(
123 ngettext(
"One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
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));
129 }
else if (n_sel > 1) {
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),
134 ngettext(
" on %d selected object",
" on %d selected objects", n_obj));
136 }
else if (n_sel == 0) {
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);
164 if (it.curve->contains(event_p,
tolerance)) {
174 std::vector<Geom::Point> coords;
175 std::vector<SPStop*> these_stops;
176 std::vector<SPStop*> next_stops;
184 for (
auto const dragger : drag->
selected) {
186 result.coords.emplace_back(dragger->point);
188 for (
auto const d : dragger->draggables) {
190 auto const gradient =
getGradient(d->item, d->fill_or_stroke);
204 auto const next_stop = this_stop->getNextStop();
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;
219 if (next_stop == last_stop) {
226 if (next_stop == last_stop) {
235 if (next_stop == last_stop) {
244 if ((std::find(
result.these_stops.begin(),
result.these_stops.end(), this_stop) ==
result.these_stops.end()) && dnext && dnext->
isSelected()) {
246 result.coords.emplace_back((dragger->point + dnext->
point) / 2);
250 result.these_stops.emplace_back(this_stop);
251 result.next_stops.emplace_back(next_stop);
264 auto draggable = dragger->draggables[0];
265 auto gradient =
getGradient(draggable->item, draggable->fill_or_stroke);
269 if (vector->getStopCount() == 1) {
271 gradient->ensureVector();
275 DocumentUndo::done(gradient->document, _(
"Add gradient stop"), INKSCAPE_ICON(
"color-gradient"));
285 for (
auto d : dragger->draggables) {
298 auto gradient =
getGradient(d->item, d->fill_or_stroke);
301 if (
auto next_stop = this_stop->getNextStop()) {
302 ret.these_stops.emplace_back(this_stop);
303 ret.next_stops.emplace_back(next_stop);
310 auto i = ret.these_stops.rbegin();
311 auto j = ret.next_stops.rbegin();
312 std::vector<SPStop *> new_stops;
315 for (; i != ret.these_stops.rend() && j != ret.next_stops.rend(); ++i, ++j) {
319 if (
auto grad = cast<SPGradient>(this_stop->
parent)) {
320 doc = grad->document;
322 new_stops.emplace_back(new_stop);
323 grad->ensureVector();
327 if (!ret.these_stops.empty() && doc) {
334 for (
auto s : new_stops) {
355 std::unordered_set<SPStop *> todel;
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) {
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()));
369 if (todel.find(stop0) != todel.end() || todel.find(stop2) != todel.end()) {
377 todel.emplace(stop1);
383 for (
auto stop : todel) {
384 doc = stop->document;
385 auto parent = stop->getRepr()->parent();
386 parent->removeChild(stop->getRepr());
389 if (!todel.empty()) {
391 drag->local_change =
true;
392 drag->updateDraggers();
393 drag->selectByCoords(ret.coords);
414 tolerance = prefs->getIntLimited(
"/options/dragtolerance/value", 0, 0, 100);
420 if (event.button != 1) {
424 if (event.num_press == 2) {
426 if (is_over_curve(event.pos)) {
429 add_stop_near_point(selection->items().front(), mousepoint_doc);
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;
435 SPGradient *vector = sp_gradient_vector_for_object(_desktop->getDocument(), _desktop, item, fsmode);
437 SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode);
438 sp_gradient_reset_to_userspace(priv, item);
444 }
else if (event.num_press == 1) {
446 saveDragOrigin(event.pos);
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);
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);
460 if (!selection->isEmpty()) {
461 auto &m = _desktop->getNamedView()->snap_manager;
463 m.freeSnapReturnByRef(button_dt, SNAPSOURCE_NODE_HANDLE);
473 [&] (MotionEvent
const &event) {
474 if (dragging && (event.modifiers & GDK_BUTTON1_MASK)) {
475 if (!checkDragMoved(event.pos)) {
479 auto const motion_dt = _desktop->w2d(event.pos);
483 defaultMessageContext()->set(
NORMAL_MESSAGE, _(
"<b>Draw around</b> handles to select them"));
485 drag(motion_dt, event.time);
492 if (!_grdrag->mouseOver() && !selection->isEmpty()) {
493 auto &m = _desktop->getNamedView()->snap_manager;
496 auto const motion_dt = _desktop->w2d(event.pos);
502 auto item = is_over_curve(event.pos);
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;
514 [&] (ButtonReleaseEvent
const &event) {
515 if (event.button != 1) {
521 auto item = is_over_curve(event.pos);
523 if ((event.modifiers & GDK_CONTROL_MASK) && (event.modifiers & GDK_ALT_MASK)) {
525 add_stop_near_point(
item, mousepoint_doc);
532 if (event.modifiers & GDK_CONTROL_MASK && !(event.modifiers & GDK_SHIFT_MASK)) {
538 if (!within_tolerance) {
543 if (r->isStarted() && !within_tolerance) {
546 _grdrag->selectRect(*r->getRectangle());
549 }
else if (item_to_select) {
555 if (event.modifiers & GDK_SHIFT_MASK) {
556 selection->toggle(item_to_select);
558 _grdrag->deselectAll();
559 selection->set(item_to_select);
564 if (!_grdrag->selected.empty()) {
565 _grdrag->deselectAll();
571 item_to_select =
nullptr;
578 [&] (KeyPressEvent
const &event) {
582 case GDK_KEY_Control_L:
583 case GDK_KEY_Control_R:
584 case GDK_KEY_Shift_L:
585 case GDK_KEY_Shift_R:
589 _(
"<b>Ctrl</b>: snap gradient angle"),
590 _(
"<b>Shift</b>: draw gradient around the starting point"),
597 _desktop->setToolboxFocusTo(
"altx-grad");
605 _grdrag->selectAll();
612 if (
mod_ctrl_only(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
619 if (!_grdrag->selected.empty()) {
620 _grdrag->deselectAll();
637 case GDK_KEY_KP_Insert:
639 add_stops_between_selected_stops();
648 add_stops_between_selected_stops();
654 case GDK_KEY_KP_Delete:
655 case GDK_KEY_BackSpace:
660 if (hasGradientDrag()) {
666 case GDK_KEY_ISO_Left_Tab:
667 if (hasGradientDrag()) {
674 ret = _grdrag->key_press_handler(event);
679 [&] (KeyReleaseEvent
const &event) {
683 case GDK_KEY_Control_L:
684 case GDK_KEY_Control_R:
685 case GDK_KEY_Shift_L:
686 case GDK_KEY_Shift_R:
689 defaultMessageContext()->clear();
697 [&] (CanvasEvent
const &event) {}
700 return ret || ToolBase::root_handler(event);
706 auto selection = _desktop->getSelection();
707 auto document = _desktop->getDocument();
709 if (!selection->isEmpty()) {
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;
715 if (item_to_select) {
721 auto items = std::vector<SPItem*>(selection->items().begin(), selection->items().end());
731 for (
auto item : selection->items()) {
751 _grdrag->updateDraggers();
754 _grdrag->local_change =
true;
757 _grdrag->grabKnot (selection->items().front(),
760 fill_or_stroke, 99999, 99999, etime);
766 int const n_objects = boost::distance(selection->items());
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),
Two-dimensional point that doubles as a vector.
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...
guint singleSelectedDraggerSingleDraggableType()
GrDragger * select_prev()
Select the knot previous from the last selected one and deselect all other selected.
void selectByStop(SPStop *stop, bool add_to_selection=true, bool override=true)
Select draggers by stop.
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)
std::set< GrDragger * > selected
std::vector< ItemCurve > item_curves
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.
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)
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
To do: update description of desktop.
double current_zoom() const
SPDocument * getDocument() const
sigc::connection connect_gradient_stop_selected(sigc::slot< void(SPStop *)> const &slot)
Inkscape::Selection * getSelection() const
bool scroll_to_point(Geom::Point const &s_dt, double autoscrollspeed=0)
Scroll screen so as to keep point 'p' visible in window.
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Typed SVG document implementation.
Base class for visual SVG elements.
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
Inkscape::Colors::Color getColor() const
std::shared_ptr< Css const > css
Editable view implementation.
static char const *const parent
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...
Macro for icon names used in Inkscape.
Interface for locally managing a current status message.
Raw stack of active status messages.
void selectNone(SPDesktop *desktop)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
bool mod_ctrl_only(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
void sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
@ SP_GRADIENT_TYPE_LINEAR
@ SP_GRADIENT_TYPE_RADIAL
bool sp_item_repr_compare_position_bool(SPObject const *first, SPObject const *second)
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...
Abstract base class for events.