Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
gradient-drag.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * On-canvas gradient dragging
4 *
5 * Authors:
6 * bulia byak <buliabyak@users.sf.net>
7 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 * Tavmjong Bah <tavmjong@free.fr>
11 *
12 * Copyright (C) 2007 Johan Engelen
13 * Copyright (C) 2005,2010 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include <cstring>
19#include <string>
20
21#include <glibmm/i18n.h>
22
23#include "colors/utils.h"
24#include "colors/color-set.h"
25
26#include "desktop-style.h"
27#include "desktop.h"
28#include "document-undo.h"
29#include "document.h"
30#include "gradient-chemistry.h"
31#include "gradient-drag.h"
32#include "selection.h"
33#include "snap.h"
34
38
41#include "object/sp-namedview.h"
43#include "object/sp-stop.h"
44#include "style.h"
45
47#include "svg/svg.h"
48
49#include "ui/icon-names.h"
50#include "ui/knot/knot.h"
51#include "ui/tools/tool-base.h"
53
54#include "xml/sp-css-attr.h"
55
58// absolute distance between gradient points for them to become a single dragger when the drag is created:
59#define MERGE_DIST 0.1
60
61// knot shapes corresponding to GrPointType enum (in sp-gradient.h)
76
77std::unordered_map<GrPointType, char const *> const gr_knot_descr = {
78 {POINT_LG_BEGIN, N_("Linear gradient <b>start</b>")},
79 {POINT_LG_END, N_("Linear gradient <b>end</b>")},
80 {POINT_LG_MID, N_("Linear gradient <b>mid stop</b>")},
81 {POINT_RG_CENTER, N_("Radial gradient <b>center</b>")},
82 {POINT_RG_R1, N_("Radial gradient <b>radius</b>")},
83 {POINT_RG_R2, N_("Radial gradient <b>radius</b>")},
84 {POINT_RG_FOCUS, N_("Radial gradient <b>focus</b>")},
85 {POINT_RG_MID1, N_("Radial gradient <b>mid stop</b>")},
86 {POINT_RG_MID2, N_("Radial gradient <b>mid stop</b>")},
87 {POINT_MG_CORNER, N_("Mesh gradient <b>corner</b>")},
88 {POINT_MG_HANDLE, N_("Mesh gradient <b>handle</b>")},
89 {POINT_MG_TENSOR, N_("Mesh gradient <b>tensor</b>")}
90};
91
92static void
94{
95 GrDrag *drag = (GrDrag *) data;
96 drag->updateDraggers ();
97 drag->updateLines ();
98 drag->updateLevels ();
99}
100
101static void gr_drag_sel_modified(Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data)
102{
103 GrDrag *drag = (GrDrag *) data;
104 if (drag->local_change) {
105 drag->refreshDraggers (); // Needed to move mesh handles and toggle visibility
106 drag->local_change = false;
107 } else {
108 drag->updateDraggers ();
109 }
110 drag->updateLines ();
111 drag->updateLevels ();
112}
113
119static int gr_drag_style_query(SPStyle *style, int property, gpointer data)
120{
121 GrDrag *drag = (GrDrag *) data;
122
123 if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_MASTEROPACITY) {
124 return QUERY_STYLE_NOTHING;
125 }
126
127 if (drag->selected.empty()) {
128 return QUERY_STYLE_NOTHING;
129 } else {
130 int ret = QUERY_STYLE_NOTHING;
131 auto colors = Colors::ColorSet();
132
133 SPStop* selected = nullptr;
134 for(auto d : drag->selected) { //for all selected draggers
135 for(auto draggable : d->draggables) { //for all draggables of dragger
136 if (ret == QUERY_STYLE_NOTHING) {
137 ret = QUERY_STYLE_SINGLE;
138 selected = sp_item_gradient_get_stop(draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
139 } else if (ret == QUERY_STYLE_SINGLE) {
141 }
142
143 colors.set(
144 draggable->item->getId(),
145 sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke)
146 );
147 }
148 }
149
150 if (!colors.isEmpty()) {
151 auto avg = colors.getAverage();
152 auto opacity = avg.stealOpacity();
153
154 // set both fill and stroke with our stop-color and stop-opacity
155 style->fill.clear();
156 style->fill.setColor(avg);
157 style->fill.set = TRUE;
158 style->fill.setTag(selected);
159 style->stroke.clear();
160 style->stroke.setColor(avg);
161 style->stroke.set = TRUE;
162 style->stroke.setTag(selected);
163
164 style->fill_opacity.set_double(opacity);
165 style->stroke_opacity.set_double(opacity);
166
167 // This seems wrong, it duplicates the opacity
168 style->opacity.set_double(opacity);
169 }
170
171 return ret;
172 }
173}
174
175Glib::ustring GrDrag::makeStopSafeColor( gchar const *str, bool &isNull )
176{
177 Glib::ustring colorStr;
178 if ( str ) {
179 isNull = false;
180 colorStr = str;
181 Glib::ustring::size_type pos = colorStr.find("url(#");
182 if ( pos != Glib::ustring::npos ) {
183 Glib::ustring targetName = colorStr.substr(pos + 5, colorStr.length() - 6);
184 std::vector<SPObject *> gradients = desktop->doc()->getResourceList("gradient");
185 for (auto gradient : gradients) {
186 auto grad = cast<SPGradient>(gradient);
187 if ( targetName == grad->getId() ) {
188 SPGradient *vect = grad->getVector();
189 SPStop *firstStop = (vect) ? vect->getFirstStop() : grad->getFirstStop();
190 if (firstStop) {
191 Glib::ustring stopColorStr = firstStop->getColor().toString();
192 if ( !stopColorStr.empty() ) {
193 colorStr = stopColorStr;
194 }
195 }
196 break;
197 }
198 }
199 }
200 } else {
201 isNull = true;
202 }
203
204 return colorStr;
205}
206
207bool GrDrag::styleSet(SPCSSAttr const *css, bool switch_style)
208{
209 if (selected.empty()) {
210 return false;
211 }
212
213 // Do not allow the stroke setter to be confused by the gradient selector
214 if (css->attribute("stroke") && !css->attribute("color") && !css->attribute("fill")) {
215 return false;
216 }
217
219
220 // See if the css contains interesting properties, and if so, translate them into the format
221 // acceptable for gradient stops
222
223 // any of color properties, in order of increasing priority:
224 if (css->attribute("flood-color")) {
225 sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color"));
226 }
227
228 if (css->attribute("lighting-color")) {
229 sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color"));
230 }
231
232 if (css->attribute("color")) {
233 sp_repr_css_set_property (stop, "stop-color", css->attribute("color"));
234 }
235
236 if (css->attribute("fill") && strcmp(css->attribute("fill"), "none")) {
237 sp_repr_css_set_property (stop, "stop-color", css->attribute("fill"));
238 }
239
240 if (css->attribute("stop-color")) {
241 sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color"));
242 }
243
244 // Make sure the style is allowed for gradient stops.
245 if ( !sp_repr_css_property_is_unset( stop, "stop-color") ) {
246 bool stopIsNull = false;
247 Glib::ustring tmp = makeStopSafeColor( sp_repr_css_property( stop, "stop-color", "" ), stopIsNull );
248 if ( !stopIsNull && !tmp.empty() ) {
249 sp_repr_css_set_property( stop, "stop-color", tmp.c_str() );
250 }
251 }
252
253
254 if (css->attribute("stop-opacity")) { // direct setting of stop-opacity has priority
255 sp_repr_css_set_property(stop, "stop-opacity", css->attribute("stop-opacity"));
256 } else { // multiply all opacity properties:
257 gdouble accumulated = 1.0;
258 accumulated *= sp_svg_read_percentage(css->attribute("flood-opacity"), 1.0);
259 accumulated *= sp_svg_read_percentage(css->attribute("opacity"), 1.0);
260 accumulated *= sp_svg_read_percentage(css->attribute("stroke-opacity"), 1.0);
261 accumulated *= sp_svg_read_percentage(css->attribute("fill-opacity"), 1.0);
262
264 os << accumulated;
265 sp_repr_css_set_property(stop, "stop-opacity", os.str().c_str());
266
267 if ((css->attribute("fill") && !css->attribute("stroke") && !strcmp(css->attribute("fill"), "none")) ||
268 (css->attribute("stroke") && !css->attribute("fill") && !strcmp(css->attribute("stroke"), "none"))) {
269 sp_repr_css_set_property(stop, "stop-opacity", "0"); // if a single fill/stroke property is set to none, don't change color, set opacity to 0
270 }
271 }
272
273 const auto& al = stop->attributeList();
274 if (al.empty()) { // nothing for us here, pass it on
276 return false;
277 }
278
279 for(auto d : selected) { //for all selected draggers
280 for(auto draggable : d->draggables) { //for all draggables of dragger
281 SPGradient* gradient = getGradient(draggable->item, draggable->fill_or_stroke);
282
283 // for linear and radial gradients F&S dialog deals with stops' colors;
284 // don't handle style notifications, or else it will not be possible to switch
285 // object style back to solid color
286 if (switch_style && gradient &&
287 (is<SPLinearGradient>(gradient) || is<SPRadialGradient>(gradient))) {
288 continue;
289 }
290
291 local_change = true;
292 sp_item_gradient_stop_set_style(draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke, stop);
293 }
294 }
295
296 //sp_repr_css_print(stop);
298 return local_change; // true if handled
299}
300
302{
303 if (selected.empty())
304 return Color(0x000000ff);
305
307 for(auto d : selected) { //for all selected draggers
308 for(auto draggable : d->draggables) { //for all draggables of dragger
309 colors.set(
310 draggable->item->getId(),
311 sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke));
312 }
313 }
314 return colors.getAverage();
315}
316
317// TODO refactor early returns
319{
320 gfloat new_stop_offset = 0; // type of SPStop.offset = gfloat
321 SPGradient *gradient = nullptr;
322 //bool r1_knot = false;
323
324 // For Mesh
325 int divide_row = -1;
326 int divide_column = -1;
327 double divide_coord = 0.5;
328
329 bool addknot = false;
330
331 for (std::vector<Inkscape::PaintTarget>::const_iterator it = allPaintTargets().begin(); (it != allPaintTargets().end()) && !addknot; ++it)
332 {
333 Inkscape::PaintTarget fill_or_stroke = *it;
334 gradient = getGradient(item, fill_or_stroke);
335 if (is<SPLinearGradient>(gradient)) {
336 Geom::Point begin = getGradientCoords(item, POINT_LG_BEGIN, 0, fill_or_stroke);
337 Geom::Point end = getGradientCoords(item, POINT_LG_END, 0, fill_or_stroke);
338 Geom::LineSegment ls(begin, end);
339 double offset = ls.nearestTime(mouse_p);
340 Geom::Point nearest = ls.pointAt(offset);
341 double dist_screen = Geom::distance(mouse_p, nearest);
342 if ( dist_screen < tolerance ) {
343 // calculate the new stop offset
344 new_stop_offset = distance(begin, nearest) / distance(begin, end);
345 // add the knot
346 addknot = true;
347 }
348 } else if (is<SPRadialGradient>(gradient)) {
349 Geom::Point begin = getGradientCoords(item, POINT_RG_CENTER, 0, fill_or_stroke);
350 Geom::Point end = getGradientCoords(item, POINT_RG_R1, 0, fill_or_stroke);
351 Geom::LineSegment ls(begin, end);
352 double offset = ls.nearestTime(mouse_p);
353 Geom::Point nearest = ls.pointAt(offset);
354 double dist_screen = Geom::distance(mouse_p, nearest);
355 if ( dist_screen < tolerance ) {
356 // calculate the new stop offset
357 new_stop_offset = distance(begin, nearest) / distance(begin, end);
358 // add the knot
359 addknot = true;
360 //r1_knot = true;
361 } else {
362 end = getGradientCoords(item, POINT_RG_R2, 0, fill_or_stroke);
363 ls = Geom::LineSegment(begin, end);
364 offset = ls.nearestTime(mouse_p);
365 nearest = ls.pointAt(offset);
366 dist_screen = Geom::distance(mouse_p, nearest);
367 if ( dist_screen < tolerance ) {
368 // calculate the new stop offset
369 new_stop_offset = distance(begin, nearest) / distance(begin, end);
370 // add the knot
371 addknot = true;
372 //r1_knot = false;
373 }
374 }
375 } else if (is<SPMeshGradient>(gradient)) {
376
377 // add_stop_near_point()
378 // Find out which curve pointer is over and use that curve to determine
379 // which row or column will be divided.
380 // This is silly as we already should know which line we are over...
381 // but that information is not saved (sp_gradient_context_is_over_line).
382
383 auto mg = cast<SPMeshGradient>(gradient);
384 Geom::Affine transform = Geom::Affine(mg->gradientTransform)*(Geom::Affine)item->i2dt_affine();
385
386 guint rows = mg->array.patch_rows();
387 guint columns = mg->array.patch_columns();
388
389 double closest = 1e10;
390 for( guint i = 0; i < rows; ++i ) {
391 for( guint j = 0; j < columns; ++j ) {
392
393 SPMeshPatchI patch( &(mg->array.nodes), i, j );
394 Geom::Point p[4];
395
396 // Top line
397 {
398 p[0] = patch.getPoint( 0, 0 ) * transform;
399 p[1] = patch.getPoint( 0, 1 ) * transform;
400 p[2] = patch.getPoint( 0, 2 ) * transform;
401 p[3] = patch.getPoint( 0, 3 ) * transform;
402 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
403 Geom::Coord coord = b.nearestTime( mouse_p );
404 Geom::Point nearest = b( coord );
405 double dist_screen = Geom::L2 ( mouse_p - nearest );
406 if ( dist_screen < closest ) {
407 closest = dist_screen;
408 divide_row = -1;
409 divide_column = j;
410 divide_coord = coord;
411 }
412 }
413
414 // Right line (only for last column)
415 if( j == columns - 1 ) {
416 p[0] = patch.getPoint( 1, 0 ) * transform;
417 p[1] = patch.getPoint( 1, 1 ) * transform;
418 p[2] = patch.getPoint( 1, 2 ) * transform;
419 p[3] = patch.getPoint( 1, 3 ) * transform;
420 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
421 Geom::Coord coord = b.nearestTime( mouse_p );
422 Geom::Point nearest = b( coord );
423 double dist_screen = Geom::L2 ( mouse_p - nearest );
424 if ( dist_screen < closest ) {
425 closest = dist_screen;
426 divide_row = i;
427 divide_column = -1;
428 divide_coord = coord;
429 }
430 }
431
432 // Bottom line (only for last row)
433 if( i == rows - 1 ) {
434 p[0] = patch.getPoint( 2, 0 ) * transform;
435 p[1] = patch.getPoint( 2, 1 ) * transform;
436 p[2] = patch.getPoint( 2, 2 ) * transform;
437 p[3] = patch.getPoint( 2, 3 ) * transform;
438 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
439 Geom::Coord coord = b.nearestTime( mouse_p );
440 Geom::Point nearest = b( coord );
441 double dist_screen = Geom::L2 ( mouse_p - nearest );
442 if ( dist_screen < closest ) {
443 closest = dist_screen;
444 divide_row = -1;
445 divide_column = j;
446 divide_coord = 1.0 - coord;
447 }
448 }
449
450 // Left line
451 {
452 p[0] = patch.getPoint( 3, 0 ) * transform;
453 p[1] = patch.getPoint( 3, 1 ) * transform;
454 p[2] = patch.getPoint( 3, 2 ) * transform;
455 p[3] = patch.getPoint( 3, 3 ) * transform;
456 Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] );
457 Geom::Coord coord = b.nearestTime( mouse_p );
458 Geom::Point nearest = b( coord );
459 double dist_screen = Geom::L2 ( mouse_p - nearest );
460 if ( dist_screen < closest ) {
461 closest = dist_screen;
462 divide_row = i;
463 divide_column = -1;
464 divide_coord = 1.0 - coord;
465 }
466 }
467
468 } // End loop over columns
469 } // End loop rows
470
471 if( closest < tolerance ) {
472 addknot = true;
473 }
474
475 } // End if mesh
476
477 }
478
479 if (addknot) {
480
481 if( is<SPLinearGradient>(gradient) || is<SPRadialGradient>( gradient ) ) {
482 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
483 SPStop* prev_stop = vector->getFirstStop();
484 SPStop* next_stop = prev_stop->getNextStop();
485 guint i = 1;
486 while ( (next_stop) && (next_stop->offset < new_stop_offset) ) {
487 prev_stop = next_stop;
488 next_stop = next_stop->getNextStop();
489 i++;
490 }
491 if (vector->getStopCount() == 1) {
492 // Handle single stop vectors separately.
493 auto newstop = sp_gradient_add_stop_at(vector, new_stop_offset);
494 gradient->ensureVector();
496
497 // so that it does not automatically update draggers in idle loop, as this would deselect
498 local_change = true;
499
500 // select the newly created stop
501 selectByStop(newstop);
502
503 return newstop;
504 } else if (!next_stop) {
505 // logical error: the endstop should have offset 1 and should always be more than this offset here
506 return nullptr;
507 }
508
509 SPStop *newstop = sp_vector_add_stop (vector, prev_stop, next_stop, new_stop_offset);
510 gradient->ensureVector();
512
513 // so that it does not automatically update draggers in idle loop, as this would deselect
514 local_change = true;
515
516 // select the newly created stop
517 selectByStop(newstop);
518
519 return newstop;
520
521 } else {
522
523 auto mg = cast<SPMeshGradient>(gradient);
524
525 if( divide_row > -1 ) {
526 mg->array.split_row( divide_row, divide_coord );
527 } else {
528 mg->array.split_column( divide_column, divide_coord );
529 }
530
531 // Update repr
532 mg->array.write( mg );
533 mg->array.built = false;
534 mg->ensureArray();
535 // How do we do this?
536 DocumentUndo::done(desktop->getDocument(), _("Added patch row or column"), INKSCAPE_ICON("mesh-gradient"));
537
538 } // Mesh
539 }
540
541 return nullptr;
542}
543
544void GrDrag::addColorToDragger(GrDragger &dragger, const char *color)
545{
546 auto stop = sp_repr_css_attr_new();
547 sp_repr_css_set_property(stop, "stop-color", color);
548 sp_repr_css_set_property(stop, "stop-opacity", "1");
549 for (auto draggable : dragger.draggables) {
550 local_change = true;
551 sp_item_gradient_stop_set_style(draggable->item, draggable->point_type, draggable->point_i,
552 draggable->fill_or_stroke, stop);
553 }
555}
556
558{
559 if (draggers.empty()) {
560 return;
561 }
562
563 if (draggers.size() == 1) {
564 // In case of single dragger. No need to find out the region,
565 // just drop the color and return early.
566 addColorToDragger(*draggers[0], color);
567 return;
568 }
569
570 // The first stop will be the center of all the discs formed
571 // by the gradient.
572 auto const center = draggers[0]->point;
573 auto const dist_point = Geom::distance(center, p);
574 double outer_radius = Geom::distance(center, draggers[1]->point) / 2;
575
576 if (dist_point < outer_radius) {
577 // Innermost stop.
578 addColorToDragger(*draggers[0], color);
579 }
580
581 for (int i = 1; i < draggers.size() - 1; ++i) {
582 // Outer radius of the previous stop is equal to the inner radius of this stop.
583 double const inner_radius = outer_radius;
584 outer_radius =
585 Geom::distance(center, draggers[i]->point) + Geom::distance(draggers[i]->point, draggers[i + 1]->point) / 2;
586
587 // The point will be inside the annulus disc if
588 // the distance of the point from the center(first stop on the gradient line)
589 // of the stop is greater than the inner radius, and less than the outer radius
590 // of the disc.
591 if (dist_point >= inner_radius && dist_point < outer_radius) {
592 addColorToDragger(*draggers[i], color);
593 }
594 }
595
596 if (dist_point >= outer_radius) {
597 // Outermost stop
598 addColorToDragger(*draggers[draggers.size() - 1], color);
599 }
600}
601
602bool GrDrag::dropColor(SPItem * /*item*/, gchar const *c, Geom::Point p)
603{
604 if (draggers.empty()) {
605 return false;
606 }
607
608 // Note: not sure if a null pointer can come in for the style, but handle that just in case
609 bool stopIsNull = false;
610 Glib::ustring toUse = makeStopSafeColor( c, stopIsNull );
611 const char *color = stopIsNull ? nullptr : toUse.c_str();
612
613 // Drop the color on the nearest dragger.
614 GrDragger *dragger = nullptr;
615
616 // Find out the dragger nearest to the mouse point.
617 double minDistance = Geom::infinity();
618 for (auto d : draggers) {
619 auto const dist = Geom::distance(p, d->point);
620 if (dist < minDistance) {
621 dragger = d;
622 minDistance = dist;
623 }
624 }
625
626 // Check if the mouse pointer is too close to a stop.
627 // In that case, do not create a new stop, instead,
628 // add the color to the nearest dragger stop.
629 double const tolerance = 5 / desktop->current_zoom();
630 bool const onDraggerStop = minDistance <= tolerance;
631
632 // Check if there's any selected draggers.
633 if (!selected.empty() && !onDraggerStop) {
634 for (auto selected_dragger : selected) {
635 addColorToDragger(*selected_dragger, color);
636 }
637 return true;
638 }
639
640 // Now see if we're over a line, we create a new stop
641 for (auto &it : item_curves) {
642 if (!onDraggerStop && it.curve->is_line() && it.item && it.curve->contains(desktop->d2w(p), 5)) {
643 if (auto stop = addStopNearPoint(it.item, p, tolerance)) {
644 auto css = sp_repr_css_attr_new();
645 sp_repr_css_set_property(css, "stop-color", color);
646 sp_repr_css_set_property( css, "stop-opacity", "1" );
647 sp_repr_css_change(stop->getRepr(), css, "style");
649 return true;
650 }
651 }
652 }
653
654 // Drop the color on the corresponding dragger.
655 auto const draggable = dragger->draggables[0];
656 if (is<SPLinearGradient>(getGradient(draggable->item, draggable->fill_or_stroke))) {
657 addColorToDragger(*dragger, color);
658 } else {
660 }
661
662 return true;
663}
664
666 keep_selection(false),
667 local_change(false),
669 hor_levels(),
670 vert_levels(),
671 draggers(0),
672 selection(desktop->getSelection()),
673 sel_changed_connection(),
674 sel_modified_connection(),
675 style_set_connection(),
676 style_query_connection()
677{
679 sigc::bind(
680 sigc::ptr_fun(&gr_drag_sel_changed),
681 (gpointer)this )
682
683 );
685 sigc::bind(
686 sigc::ptr_fun(&gr_drag_sel_modified),
687 (gpointer)this )
688 );
689
691
693 sigc::bind(
694 sigc::ptr_fun(&gr_drag_style_query),
695 (gpointer)this )
696 );
697
699 updateLines();
700 updateLevels();
701
702 if (desktop->gr_item) {
704 if (dragger) {
705 setSelected(dragger);
706 }
707 }
708}
709
711{
712 this->sel_changed_connection.disconnect();
713 this->sel_modified_connection.disconnect();
714 this->style_set_connection.disconnect();
715 this->style_query_connection.disconnect();
716
717 if (! this->selected.empty()) {
718 GrDraggable *draggable = (*(this->selected.begin()))->draggables[0];
719 desktop->gr_item = draggable->item;
720 desktop->gr_point_type = draggable->point_type;
721 desktop->gr_point_i = draggable->point_i;
723 } else {
724 desktop->gr_item = nullptr;
726 desktop->gr_point_i = 0;
728 }
729
730 deselect_all();
731 for (auto dragger : this->draggers) {
732 delete dragger;
733 }
734 this->draggers.clear();
735 this->selected.clear();
736
737 item_curves.clear();
738}
739
741 item(item),
743 point_i(point_i),
744 fill_or_stroke(fill_or_stroke)
745{
747}
748
753
755{
756 SPObject *server = nullptr;
757 if (item) {
758 switch (fill_or_stroke) {
760 server = item->style->getFillPaintServer();
761 break;
763 server = item->style->getStrokePaintServer();
764 break;
765 }
766 }
767
768 return server;
769}
770
771static void gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gpointer data)
772{
773 GrDragger *dragger = (GrDragger *) data;
774
775 // Dragger must have at least one draggable
776 GrDraggable *draggable = (GrDraggable *) dragger->draggables[0];
777 if (!draggable) return;
778
779 // Find mesh corner that corresponds to dragger (only checks first draggable) and highlight it.
780 GrDragger *dragger_corner = dragger->getMgCorner();
781 if (dragger_corner) {
782 dragger_corner->highlightCorner(true);
783 }
784
785 // Set-up snapping
786 SPDesktop *desktop = dragger->parent->desktop;
787 auto &m = desktop->getNamedView()->snap_manager;
788 double snap_dist = m.snapprefs.getObjectTolerance() / dragger->parent->desktop->current_zoom();
789
790 Geom::Point p = ppointer;
791
792 if (state & GDK_SHIFT_MASK) {
793 // with Shift; unsnap if we carry more than one draggable
794 if (dragger->draggables.size()>1) {
795 // create a new dragger
796 GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, nullptr);
797 dragger->parent->draggers.insert(dragger->parent->draggers.begin(), dr_new);
798 // relink to it all but the first draggable in the list
799 std::vector<GrDraggable *>::const_iterator i = dragger->draggables.begin();
800 for ( ++i ; i != dragger->draggables.end(); ++i ) {
801 GrDraggable *draggable = *i;
802 dr_new->addDraggable (draggable);
803 }
804 dr_new->updateKnotShape();
805 if(dragger->draggables.size()>1){
806 GrDraggable *tmp = dragger->draggables[0];
807 dragger->draggables.clear();
808 dragger->draggables.push_back(tmp);
809 }
810 dragger->updateKnotShape();
811 dragger->updateTip();
812 }
813 } else if (!(state & GDK_CONTROL_MASK)) {
814 // without Shift or Ctrl; see if we need to snap to another dragger
815 for (std::vector<GrDragger *>::const_iterator di = dragger->parent->draggers.begin(); di != dragger->parent->draggers.end() ; ++di) {
816 GrDragger *d_new = *di;
817 if (dragger->mayMerge(d_new) && Geom::L2 (d_new->point - p) < snap_dist) {
818
819 // Merge draggers:
820 for (auto draggable : dragger->draggables) {
821 // copy draggable to d_new:
822 GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
823 d_new->addDraggable (da_new);
824 }
825
826 // unlink and delete this dragger
827 dragger->parent->draggers.erase(std::remove(dragger->parent->draggers.begin(),dragger->parent->draggers.end(), dragger),dragger->parent->draggers.end());
828 d_new->parent->draggers.erase(std::remove(d_new->parent->draggers.begin(),d_new->parent->draggers.end(), dragger),d_new->parent->draggers.end());
829 d_new->parent->selected.erase(dragger);
830 delete dragger;
831
832 // throw out delayed snap context
834
835 // update the new merged dragger
836 d_new->fireDraggables(true, false, true);
837 d_new->parent->updateLines();
838 d_new->parent->setSelected (d_new);
839 d_new->updateKnotShape ();
840 d_new->updateTip ();
841 d_new->updateDependencies(true);
842 DocumentUndo::done(d_new->parent->desktop->getDocument(), _("Merge gradient handles"), INKSCAPE_ICON("color-gradient"));
843 return;
844 }
845 }
846 }
847
848 if (!((state & GDK_SHIFT_MASK) || (state & GDK_CONTROL_MASK))) {
849 m.setup(desktop);
851 m.unSetup();
852 if (s.getSnapped()) {
853 p = s.getPoint();
854 knot->moveto(p);
855 }
856 } else if (state & GDK_CONTROL_MASK) {
860 unsigned snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12));
861 /* 0 means no snapping. */
862
863 for (std::vector<GrDraggable *>::const_iterator i = dragger->draggables.begin(); i != dragger->draggables.end(); ++i) {
864 GrDraggable *draggable = *i;
865
867
868 if (draggable->point_type == POINT_LG_BEGIN || draggable->point_type == POINT_LG_END) {
869 for (std::vector<GrDragger *>::const_iterator di = dragger->parent->draggers.begin() ; di != dragger->parent->draggers.end() ; ++di) {
870 GrDragger *d_new = *di;
871 if (d_new == dragger)
872 continue;
873 if (d_new->isA (draggable->item,
875 draggable->fill_or_stroke)) {
876 // found the other end of the linear gradient;
877 if (state & GDK_SHIFT_MASK) {
878 // moving linear around center
879 Geom::Point center = Geom::Point (0.5*(d_new->point + dragger->point));
880 dr_snap = center;
881 } else {
882 // moving linear around the other end
883 dr_snap = d_new->point;
884 }
885 }
886 }
887 } else if (draggable->point_type == POINT_RG_R1 || draggable->point_type == POINT_RG_R2 || draggable->point_type == POINT_RG_FOCUS) {
888 for (std::vector<GrDragger *>::const_iterator di = dragger->parent->draggers.begin(); di != dragger->parent->draggers.end(); ++di) {
889 GrDragger *d_new = *di;
890 if (d_new == dragger)
891 continue;
892 if (d_new->isA (draggable->item,
894 draggable->fill_or_stroke)) {
895 // found the center of the radial gradient;
896 dr_snap = d_new->point;
897 }
898 }
899 } else if (draggable->point_type == POINT_RG_CENTER) {
900 // radial center snaps to hor/vert relative to its original position
901 dr_snap = dragger->point_original;
902 } else if (draggable->point_type == POINT_MG_CORNER ||
903 draggable->point_type == POINT_MG_HANDLE ||
904 draggable->point_type == POINT_MG_TENSOR ) {
905 // std::cout << " gr_knot_moved_handler: Got mesh point!" << std::endl;
906 }
907
908 // dr_snap contains the origin of the gradient, whereas p will be the new endpoint which we will try to snap now
910 if (dr_snap.isFinite()) {
911 m.setup(desktop);
912 if (state & GDK_ALT_MASK) {
913 // with Alt, snap to the original angle and its perpendiculars
914 sp = m.constrainedAngularSnap(scp, dragger->point_original, dr_snap, 2);
915 } else {
916 // with Ctrl, snap to M_PI/snaps
917 sp = m.constrainedAngularSnap(scp, std::optional<Geom::Point>(), dr_snap, snaps);
918 }
919 m.unSetup();
920 isr.points.push_back(sp);
921 }
922 }
923
924 m.setup(desktop, false); // turn of the snap indicator temporarily
925 Inkscape::SnappedPoint bsp = m.findBestSnap(scp, isr, true);
926 m.unSetup();
927 if (!bsp.getSnapped()) {
928 // If we didn't truly snap to an object or to a grid, then we will still have to look for the
929 // closest projection onto one of the constraints. findBestSnap() will not do this for us
930 for (std::list<Inkscape::SnappedPoint>::const_iterator i = isr.points.begin(); i != isr.points.end(); ++i) {
931 if (i == isr.points.begin() || (Geom::L2((*i).getPoint() - p) < Geom::L2(bsp.getPoint() - p))) {
932 bsp.setPoint((*i).getPoint());
934 }
935 }
936 }
937 //p = isr.points.front().getPoint();
938 p = bsp.getPoint();
939 knot->moveto(p);
940 }
941
942 GrDrag *drag = dragger->parent; // There is just one GrDrag.
943 drag->keep_selection = (drag->selected.find(dragger)!=drag->selected.end());
944 bool scale_radial = (state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK);
945
946 if (drag->keep_selection) {
947 Geom::Point diff = p - dragger->point;
948 drag->selected_move_nowrite (diff[Geom::X], diff[Geom::Y], scale_radial);
949 } else {
950 Geom::Point p_old = dragger->point;
951 dragger->point = p;
952 dragger->fireDraggables (false, scale_radial);
953 dragger->updateDependencies(false);
954 dragger->moveMeshHandles( p_old, MG_NODE_NO_SCALE );
955 }
956}
957
958
959static void gr_midpoint_limits(GrDragger *dragger, SPObject *server, Geom::Point *begin, Geom::Point *end, Geom::Point *low_lim, Geom::Point *high_lim, std::vector<GrDragger *> &moving)
960{
961
962 GrDrag *drag = dragger->parent;
963 // a midpoint dragger can (logically) only contain one GrDraggable
964 GrDraggable *draggable = dragger->draggables[0];
965
966 // get begin and end points between which dragging is allowed:
967 // the draglimits are between knot(lowest_i - 1) and knot(highest_i + 1)
968 moving.push_back(dragger);
969
970 guint lowest_i = draggable->point_i;
971 guint highest_i = draggable->point_i;
972 GrDragger *lowest_dragger = dragger;
973 GrDragger *highest_dragger = dragger;
974 if (dragger->isSelected()) {
975 GrDragger* d_add;
976 while ( true )
977 {
978 d_add = drag->getDraggerFor(draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
979 if ( d_add && drag->selected.find(d_add)!=drag->selected.end() ) {
980 lowest_i = lowest_i - 1;
981 moving.insert(moving.begin(),d_add);
982 lowest_dragger = d_add;
983 } else {
984 break;
985 }
986 }
987
988 while ( true )
989 {
990 d_add = drag->getDraggerFor(draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
991 if ( d_add && drag->selected.find(d_add)!=drag->selected.end() ) {
992 highest_i = highest_i + 1;
993 moving.push_back(d_add);
994 highest_dragger = d_add;
995 } else {
996 break;
997 }
998 }
999 }
1000
1001 if ( is<SPLinearGradient>(server) ) {
1002 guint num = cast<SPLinearGradient>(server)->vector.stops.size();
1003 GrDragger *d_temp;
1004 if (lowest_i == 1) {
1005 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke);
1006 } else {
1007 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, lowest_i - 1, draggable->fill_or_stroke);
1008 }
1009 if (d_temp)
1010 *begin = d_temp->point;
1011
1012 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, highest_i + 1, draggable->fill_or_stroke);
1013 if (d_temp == nullptr) {
1014 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_END, num-1, draggable->fill_or_stroke);
1015 }
1016 if (d_temp)
1017 *end = d_temp->point;
1018 } else if ( is<SPRadialGradient>(server) ) {
1019 guint num = cast<SPRadialGradient>(server)->vector.stops.size();
1020 GrDragger *d_temp;
1021 if (lowest_i == 1) {
1022 d_temp = drag->getDraggerFor (draggable->item, POINT_RG_CENTER, 0, draggable->fill_or_stroke);
1023 } else {
1024 d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
1025 }
1026 if (d_temp)
1027 *begin = d_temp->point;
1028
1029 d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
1030 if (d_temp == nullptr) {
1031 d_temp = drag->getDraggerFor (draggable->item, (draggable->point_type==POINT_RG_MID1) ? POINT_RG_R1 : POINT_RG_R2, num-1, draggable->fill_or_stroke);
1032 }
1033 if (d_temp)
1034 *end = d_temp->point;
1035 }
1036
1037 *low_lim = dragger->point - (lowest_dragger->point - *begin);
1038 *high_lim = dragger->point - (highest_dragger->point - *end);
1039}
1040
1044static void gr_knot_moved_midpoint_handler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state, gpointer data)
1045{
1046 GrDragger *dragger = (GrDragger *) data;
1047 GrDrag *drag = dragger->parent;
1048 // a midpoint dragger can (logically) only contain one GrDraggable
1049 GrDraggable *draggable = dragger->draggables[0];
1050
1051 // FIXME: take from prefs
1052 double snap_fraction = 0.1;
1053
1054 Geom::Point p = ppointer;
1055 Geom::Point begin(0,0), end(0,0);
1056 Geom::Point low_lim(0,0), high_lim(0,0);
1057
1058 SPObject *server = draggable->getServer();
1059
1060 std::vector<GrDragger *> moving;
1061 gr_midpoint_limits(dragger, server, &begin, &end, &low_lim, &high_lim, moving);
1062
1063 if (state & GDK_CONTROL_MASK) {
1064 Geom::LineSegment ls(low_lim, high_lim);
1065 p = ls.pointAt(round(ls.nearestTime(p) / snap_fraction) * snap_fraction);
1066 } else {
1067 Geom::LineSegment ls(low_lim, high_lim);
1068 p = ls.pointAt(ls.nearestTime(p));
1069 if (!(state & GDK_SHIFT_MASK)) {
1070 Inkscape::Snapper::SnapConstraint cl(low_lim, high_lim - low_lim);
1071 SPDesktop *desktop = dragger->parent->desktop;
1072 auto &m = desktop->getNamedView()->snap_manager;
1073 m.setup(desktop);
1074 m.constrainedSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE, cl);
1075 m.unSetup();
1076 }
1077 }
1078 Geom::Point displacement = p - dragger->point;
1079
1080 for (auto drg : moving) {
1081 SPKnot *drgknot = drg->knot;
1082 Geom::Point this_move = displacement;
1083 if (state & GDK_ALT_MASK) {
1084 // FIXME: unify all these profiles (here, in nodepath, in tweak) in one place
1085 double alpha = 1.0;
1086 if (Geom::L2(drg->point - dragger->point) + Geom::L2(drg->point - begin) - 1e-3 > Geom::L2(dragger->point - begin)) { // drg is on the end side from dragger
1087 double x = Geom::L2(drg->point - dragger->point)/Geom::L2(end - dragger->point);
1088 this_move = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5) * this_move;
1089 } else { // drg is on the begin side from dragger
1090 double x = Geom::L2(drg->point - dragger->point)/Geom::L2(begin - dragger->point);
1091 this_move = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5) * this_move;
1092 }
1093 }
1094 drg->point += this_move;
1095 drgknot->moveto(drg->point);
1096 drg->fireDraggables (false);
1097 drg->updateDependencies(false);
1098 }
1099
1100 drag->keep_selection = dragger->isSelected();
1101}
1102
1103
1104
1105static void gr_knot_mousedown_handler(SPKnot */*knot*/, unsigned int /*state*/, gpointer data)
1106{
1107 GrDragger *dragger = (GrDragger *) data;
1108 GrDrag *drag = dragger->parent;
1109
1110 // Turn off all mesh handle highlighting
1111 for(auto d : drag->draggers) { //for all selected draggers
1112 d->highlightCorner(false);
1113 }
1114
1115 // Highlight only mesh corner corresponding to grabbed corner or handle
1116 GrDragger *dragger_corner = dragger->getMgCorner();
1117 if (dragger_corner) {
1118 dragger_corner->highlightCorner(true);
1119 }
1120}
1121
1125static void gr_knot_ungrabbed_handler(SPKnot *knot, unsigned int state, gpointer data)
1126{
1127 GrDragger *dragger = (GrDragger *) data;
1128
1129 dragger->point_original = dragger->point = knot->pos;
1130
1131 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
1132 dragger->fireDraggables (true, true);
1133 } else {
1134 dragger->fireDraggables (true);
1135 }
1136 dragger->moveMeshHandles( dragger->point_original, MG_NODE_NO_SCALE );
1137
1138 for (std::set<GrDragger *>::const_iterator it = dragger->parent->selected.begin(); it != dragger->parent->selected.end() ; ++it ) {
1139 if (*it == dragger)
1140 continue;
1141 (*it)->fireDraggables (true);
1142 }
1143
1144 // make this dragger selected
1145 if (!dragger->parent->keep_selection) {
1146 dragger->parent->setSelected (dragger);
1147 }
1148 dragger->parent->keep_selection = false;
1149
1150 dragger->updateDependencies(true);
1151
1152 // we did an undoable action
1153 DocumentUndo::done(dragger->parent->desktop->getDocument(), _("Move gradient handle"), INKSCAPE_ICON("color-gradient"));
1154}
1155
1160static void gr_knot_clicked_handler(SPKnot */*knot*/, guint state, gpointer data)
1161{
1162 GrDragger *dragger = (GrDragger *) data;
1163 GrDraggable *draggable = dragger->draggables[0];
1164 if (!draggable) return;
1165
1166 if ( (state & GDK_CONTROL_MASK) && (state & GDK_ALT_MASK ) ) {
1167 // delete this knot from vector
1168 SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke);
1169 gradient = gradient->getVector();
1170 if (gradient->vector.stops.size() > 2) { // 2 is the minimum
1171 SPStop *stop = nullptr;
1172 switch (draggable->point_type) { // if we delete first or last stop, move the next/previous to the edge
1173
1174 case POINT_LG_BEGIN:
1175 case POINT_RG_CENTER:
1176 stop = gradient->getFirstStop();
1177 {
1178 SPStop *next = stop->getNextStop();
1179 if (next) {
1180 next->offset = 0;
1181 next->getRepr()->setAttributeCssDouble("offset", 0);
1182 }
1183 }
1184 break;
1185
1186 case POINT_LG_END:
1187 case POINT_RG_R1:
1188 case POINT_RG_R2:
1189 stop = sp_last_stop(gradient);
1190 {
1191 SPStop *prev = stop->getPrevStop();
1192 if (prev) {
1193 prev->offset = 1;
1194 prev->getRepr()->setAttributeCssDouble("offset", 1);
1195 }
1196 }
1197 break;
1198
1199 case POINT_LG_MID:
1200 case POINT_RG_MID1:
1201 case POINT_RG_MID2:
1202 stop = sp_get_stop_i(gradient, draggable->point_i);
1203 break;
1204
1205 default:
1206 return;
1207
1208 }
1209
1210 gradient->getRepr()->removeChild(stop->getRepr());
1211 DocumentUndo::done(gradient->document, _("Delete gradient stop"), INKSCAPE_ICON("color-gradient"));
1212 }
1213 } else {
1214 // select the dragger
1215
1216 dragger->point_original = dragger->point;
1217
1218 if ( state & GDK_SHIFT_MASK ) {
1219 dragger->parent->setSelected (dragger, true, false);
1220 } else {
1221 dragger->parent->setSelected (dragger);
1222 }
1223 }
1224}
1225
1229static void gr_knot_doubleclicked_handler(SPKnot */*knot*/, guint /*state*/, gpointer data)
1230{
1231 GrDragger *dragger = (GrDragger *) data;
1232
1233 dragger->point_original = dragger->point;
1234
1235 if (dragger->draggables.empty()) {
1236 return;
1237 } else {
1238 auto drag = dragger->parent;
1239 auto draggable = dragger->draggables[0];
1240 auto gradient = getGradient(draggable->item, draggable->fill_or_stroke);
1241 auto vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
1242
1243 // Treat single stop gradients separately.
1244 if (vector->getStopCount() == 1) {
1245 auto first_stop = vector->getFirstStop();
1246 bool is_offset_zero = first_stop->offset < 1e-4;
1247 bool is_first_dragger = dragger == drag->draggers[0];
1248
1249 // Do not add new stop if the double clicked dragger already has a stop.
1250 if ((is_offset_zero && is_first_dragger) || (!is_offset_zero && !is_first_dragger)) {
1251 return;
1252 }
1253
1254 auto newstop = sp_gradient_add_stop(vector, first_stop);
1255 gradient->ensureVector();
1256 drag->updateDraggers();
1257 drag->local_change = true;
1258 drag->selectByStop(newstop);
1259 DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient"));
1260 }
1261 }
1262}
1263
1267void GrDragger::fireDraggables(bool write_repr, bool scale_radial, bool merging_focus)
1268{
1269 for (auto draggable : this->draggables) {
1270 // set local_change flag so that selection_changed callback does not regenerate draggers
1271 this->parent->local_change = true;
1272
1273 // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
1274 // to the center, unless it's the first update upon merge when we must snap it to the point
1275 if (merging_focus ||
1276 !(draggable->point_type == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->point_i, draggable->fill_or_stroke)))
1277 {
1278 sp_item_gradient_set_coords (draggable->item, draggable->point_type, draggable->point_i, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
1279 }
1280 }
1281}
1282
1284{
1285 this->knot->updateCtrl();
1286 this->updateKnotShape();
1287}
1288
1293{
1294 for (auto draggable : this->draggables) {
1295 if (draggable->point_type == point_type) {
1296 return true;
1297 }
1298 }
1299 return false;
1300}
1301
1306{
1307 for (auto draggable : this->draggables) {
1308 if ( (draggable->point_type == point_type) && (draggable->point_i == point_i) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
1309 return true;
1310 }
1311 }
1312 return false;
1313}
1314
1319{
1320 for (auto draggable : this->draggables) {
1321 if ( (draggable->point_type == point_type) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
1322 return true;
1323 }
1324 }
1325 return false;
1326}
1327
1329{
1330 if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
1331 // we must not merge the points of the same gradient!
1332 if (!((this->point_type == POINT_RG_FOCUS && da2->point_type == POINT_RG_CENTER) ||
1333 (this->point_type == POINT_RG_CENTER && da2->point_type == POINT_RG_FOCUS))) {
1334 // except that we can snap center and focus together
1335 return false;
1336 }
1337 }
1338 // disable merging of midpoints.
1339 if ( (this->point_type == POINT_LG_MID) || (da2->point_type == POINT_LG_MID)
1340 || (this->point_type == POINT_RG_MID1) || (da2->point_type == POINT_RG_MID1)
1341 || (this->point_type == POINT_RG_MID2) || (da2->point_type == POINT_RG_MID2) )
1342 return false;
1343
1344 return true;
1345}
1346
1348{
1349 if (this == other)
1350 return false;
1351
1352 for (auto da1 : this->draggables) {
1353 for (auto da2 : other->draggables) {
1354 if (!da1->mayMerge(da2))
1355 return false;
1356 }
1357 }
1358 return true;
1359}
1360
1362{
1363 for (auto da1 : this->draggables) {
1364 if (!da1->mayMerge(da2))
1365 return false;
1366 }
1367 return true;
1368}
1369
1381void
1383{
1384 // This routine might more properly be in mesh-context.cpp but moving knots is
1385 // handled here rather than there.
1386
1387 // We need to update two places:
1388 // 1. In SPMeshArrayI with object coordinates
1389 // 2. In Drager/Knots with desktop coordinates.
1390
1391 // This routine is more complicated than it might need to be inorder to allow
1392 // corner points to be selected in multiple meshes at the same time... with some
1393 // sharing the same dragger (overkill, perhaps?).
1394
1395 // If no corner point in GrDragger then do nothing.
1396 if( !isA (POINT_MG_CORNER ) ) return;
1397
1398 GrDrag *drag = this->parent;
1399
1400 // We need a list of selected corners per mesh if scaling.
1401 std::map<SPGradient*, std::vector<guint> > selected_corners;
1402 // scaling was disabled so #if 0'ing out for now.
1403#if 0
1404 const bool scale = false;
1405 if( scale ) {
1406
1407 for( std::set<GrDragger *>::const_iterator it = drag->selected.begin(); it != drag->selected.end(); ++it ) {
1408 GrDragger *dragger = *it;
1409 for (std::vector<GrDraggable *>::const_iterator it2 = dragger->draggables.begin(); it2 != dragger->draggables.end(); ++it2 ) {
1410 GrDraggable *draggable = *it2;
1411
1412 // Check draggable is of type POINT_MG_CORNER (don't allow selection of POINT_MG_HANDLE)
1413 if( draggable->point_type != POINT_MG_CORNER ) continue;
1414
1415 // Must be a mesh gradient
1416 SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke);
1417 if ( !is<SPMeshGradient>( gradient ) ) continue;
1418
1419 selected_corners[ gradient ].push_back( draggable->point_i );
1420 }
1421 }
1422 }
1423#endif
1424
1425 // Now we do the handle moves.
1426
1427 // Loop over all draggables in moved corner
1428 std::map<SPGradient*, std::vector<guint> > dragger_corners;
1429 for (auto draggable : draggables) {
1430 SPItem *item = draggable->item;
1431 gint point_type = draggable->point_type;
1432 gint point_i = draggable->point_i;
1434 fill_or_stroke = draggable->fill_or_stroke;
1435
1436 // Check draggable is of type POINT_MG_CORNER (don't allow selection of POINT_MG_HANDLE)
1437 if( point_type != POINT_MG_CORNER ) continue;
1438
1439 // Must be a mesh gradient
1440 SPGradient *gradient = getGradient(item, fill_or_stroke);
1441 if ( !is<SPMeshGradient>( gradient ) ) continue;
1442 auto mg = cast<SPMeshGradient>( gradient );
1443
1444 // pc_old is the old corner position in desktop coordinates, we need it in gradient coordinate.
1445 gradient = sp_gradient_convert_to_userspace (gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke");
1446 Geom::Affine i2d ( item->i2dt_affine() );
1447 Geom::Point pcg_old = pc_old * i2d.inverse();
1448 pcg_old *= (gradient->gradientTransform).inverse();
1449
1450 mg->array.update_handles( point_i, selected_corners[ gradient ], pcg_old, op );
1451 mg->array.write( mg );
1452
1453 // Move on-screen knots
1454 for( guint i = 0; i < mg->array.handles.size(); ++i ) {
1455 GrDragger *handle = drag->getDraggerFor( item, POINT_MG_HANDLE, i, fill_or_stroke );
1456 SPKnot *knot = handle->knot;
1457 Geom::Point pk = getGradientCoords( item, POINT_MG_HANDLE, i, fill_or_stroke );
1458 knot->moveto(pk);
1459
1460 }
1461
1462 for( guint i = 0; i < mg->array.tensors.size(); ++i ) {
1463
1464 GrDragger *handle = drag->getDraggerFor( item, POINT_MG_TENSOR, i, fill_or_stroke );
1465 SPKnot *knot = handle->knot;
1466 Geom::Point pk = getGradientCoords( item, POINT_MG_TENSOR, i, fill_or_stroke );
1467 knot->moveto(pk);
1468
1469 }
1470
1471 } // Loop over draggables.
1472}
1473
1474
1479{
1480 g_return_if_fail(this->knot != nullptr);
1481
1482 char *tip = nullptr;
1483
1484 if (this->draggables.size() == 1) {
1485 GrDraggable *draggable = this->draggables[0];
1486 char *item_desc = draggable->item->detailedDescription();
1487 switch (draggable->point_type) {
1488 case POINT_LG_MID:
1489 case POINT_RG_MID1:
1490 case POINT_RG_MID2:
1491 tip = g_strdup_printf (_("%s %d for: %s%s; drag with <b>Ctrl</b> to snap offset; click with <b>Ctrl+Alt</b> to delete stop"),
1492 _(gr_knot_descr.at(draggable->point_type)),
1493 draggable->point_i,
1494 item_desc,
1495 (draggable->fill_or_stroke == Inkscape::FOR_STROKE) ? _(" (stroke)") : "");
1496 break;
1497
1498 case POINT_MG_CORNER:
1499 case POINT_MG_HANDLE:
1500 case POINT_MG_TENSOR:
1501 tip = g_strdup_printf (_("%s for: %s%s"),
1502 _(gr_knot_descr.at(draggable->point_type)),
1503 item_desc,
1504 (draggable->fill_or_stroke == Inkscape::FOR_STROKE) ? _(" (stroke)") : "");
1505 break;
1506
1507 default:
1508 tip = g_strdup_printf (_("%s for: %s%s; drag with <b>Ctrl</b> to snap angle, with <b>Ctrl+Alt</b> to preserve angle, with <b>Ctrl+Shift</b> to scale around center"),
1509 _(gr_knot_descr.at(draggable->point_type)),
1510 item_desc,
1511 (draggable->fill_or_stroke == Inkscape::FOR_STROKE) ? _(" (stroke)") : "");
1512 break;
1513 }
1514 g_free(item_desc);
1515 } else if (draggables.size() == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
1516 tip = g_strdup_printf ("%s", _("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
1517 } else {
1518 int length = this->draggables.size();
1519 tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
1520 "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
1521 length),
1522 length);
1523 }
1524
1525 knot->setTip(tip);
1526 g_free(tip);
1527}
1528
1533{
1534 if (draggables.empty())
1535 return;
1536 GrDraggable *last = draggables.back();
1537
1538 this->knot->ctrl->set_type(gr_knot_types.at(last->point_type));
1539}
1540
1545{
1546 this->draggables.insert(this->draggables.begin(), draggable);
1547
1548 this->updateTip();
1549}
1550
1551
1555void GrDragger::moveThisToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr)
1556{
1557 if (draggables.empty())
1558 return;
1559
1560 GrDraggable *dr_first = draggables[0];
1561
1562 this->point = getGradientCoords(dr_first->item, dr_first->point_type, dr_first->point_i, dr_first->fill_or_stroke);
1563 this->point_original = this->point;
1564
1565 this->knot->moveto(this->point);
1566
1567 for (auto da : draggables) {
1568 if ( (da->item == item) &&
1569 (da->point_type == point_type) &&
1570 (point_i == -1 || da->point_i == point_i) &&
1571 (da->fill_or_stroke == fill_or_stroke) ) {
1572 // Don't move initial draggable
1573 continue;
1574 }
1575 sp_item_gradient_set_coords(da->item, da->point_type, da->point_i, this->point, da->fill_or_stroke, write_repr, false);
1576 }
1577 // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
1578}
1579
1580
1584void GrDragger::updateMidstopDependencies(GrDraggable *draggable, bool write_repr)
1585{
1586 SPObject *server = draggable->getServer();
1587 if (!server)
1588 return;
1589 guint num = cast<SPGradient>(server)->vector.stops.size();
1590 if (num <= 2) return;
1591
1592 if ( is<SPLinearGradient>(server) ) {
1593 for ( guint i = 1; i < num - 1; i++ ) {
1594 this->moveOtherToDraggable (draggable->item, POINT_LG_MID, i, draggable->fill_or_stroke, write_repr);
1595 }
1596 } else if ( is<SPRadialGradient>(server) ) {
1597 for ( guint i = 1; i < num - 1; i++ ) {
1598 this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, i, draggable->fill_or_stroke, write_repr);
1599 this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, i, draggable->fill_or_stroke, write_repr);
1600 }
1601 }
1602}
1603
1604
1609{
1610 for (auto draggable : draggables) {
1611 switch (draggable->point_type) {
1612 case POINT_LG_BEGIN:
1613 {
1614 // the end point is dependent only when dragging with ctrl+shift
1615 this->moveOtherToDraggable (draggable->item, POINT_LG_END, -1, draggable->fill_or_stroke, write_repr);
1616
1617 this->updateMidstopDependencies (draggable, write_repr);
1618 }
1619 break;
1620 case POINT_LG_END:
1621 {
1622 // the begin point is dependent only when dragging with ctrl+shift
1623 this->moveOtherToDraggable (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke, write_repr);
1624
1625 this->updateMidstopDependencies (draggable, write_repr);
1626 }
1627 break;
1628 case POINT_LG_MID:
1629 // no other nodes depend on mid points.
1630 break;
1631 case POINT_RG_R2:
1632 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, -1, draggable->fill_or_stroke, write_repr);
1633 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
1634 this->updateMidstopDependencies (draggable, write_repr);
1635 break;
1636 case POINT_RG_R1:
1637 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, -1, draggable->fill_or_stroke, write_repr);
1638 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
1639 this->updateMidstopDependencies (draggable, write_repr);
1640 break;
1641 case POINT_RG_CENTER:
1642 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, -1, draggable->fill_or_stroke, write_repr);
1643 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, -1, draggable->fill_or_stroke, write_repr);
1644 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
1645 this->updateMidstopDependencies (draggable, write_repr);
1646 break;
1647 case POINT_RG_FOCUS:
1648 // nothing can depend on that
1649 break;
1650 case POINT_RG_MID1:
1651 this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, draggable->point_i, draggable->fill_or_stroke, write_repr);
1652 break;
1653 case POINT_RG_MID2:
1654 this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, draggable->point_i, draggable->fill_or_stroke, write_repr);
1655 break;
1656 default:
1657 break;
1658 }
1659 }
1660}
1661
1662
1663
1665 : point(p),
1666 point_original(p)
1667{
1668 this->draggables.clear();
1669
1670 this->parent = parent;
1671
1672
1673 // create the knot
1674 this->knot = new SPKnot(parent->desktop, "", Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "CanvasItemCtrl::GrDragger");
1675 this->knot->updateCtrl();
1676
1677 // move knot to the given point
1679 this->knot->show();
1680
1681 // connect knot's signals
1682 if ( (draggable) // it can be NULL if a node in unsnapped (eg. focus point unsnapped from center)
1683 // luckily, midstops never snap to other nodes so are never unsnapped...
1684 && ( (draggable->point_type == POINT_LG_MID)
1685 || (draggable->point_type == POINT_RG_MID1)
1686 || (draggable->point_type == POINT_RG_MID2) ) )
1687 {
1688 this->_moved_connection = this->knot->moved_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_moved_midpoint_handler), this));
1689 } else {
1690 this->_moved_connection = this->knot->moved_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_moved_handler), this));
1691 }
1692
1693 this->_clicked_connection =
1694 this->knot->click_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_clicked_handler), this));
1695
1697 this->knot->doubleclicked_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_doubleclicked_handler), this));
1698
1699 this->_mousedown_connection =
1700 this->knot->mousedown_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_mousedown_handler), this));
1701
1702 this->_ungrabbed_connection =
1703 this->knot->ungrabbed_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_ungrabbed_handler), this));
1704
1705 // add the initial draggable
1706 if (draggable) {
1707 this->addDraggable (draggable);
1708 }
1709
1711}
1712
1714{
1715 // unselect if it was selected
1716 // Hmm, this causes a race condition as it triggers a call to gradient_selection_changed which
1717 // can be executed while a list of draggers is being deleted. It doesn't actually seem to be
1718 // necessary.
1719 //this->parent->setDeselected(this);
1720
1721 // disconnect signals
1722 this->_moved_connection.disconnect();
1723 this->_clicked_connection.disconnect();
1724 this->_doubleclicked_connection.disconnect();
1725 this->_mousedown_connection.disconnect();
1726 this->_ungrabbed_connection.disconnect();
1727
1728 // unref should call destroy
1730
1731 // delete all draggables
1732 for (auto draggable : this->draggables) {
1733 delete draggable;
1734 }
1735 this->draggables.clear();
1736}
1737
1742 for (auto dragger : this->draggers) {
1743 for (std::vector<GrDraggable *>::const_iterator j = dragger->draggables.begin(); j != dragger->draggables.end(); ++j ) {
1744 if (d == *j) {
1745 return dragger;
1746 }
1747 }
1748 }
1749 return nullptr;
1750}
1751
1756{
1757 for (auto dragger : this->draggers) {
1758 for (std::vector<GrDraggable *>::const_iterator j = dragger->draggables.begin(); j != dragger->draggables.end(); ++j ) {
1759 GrDraggable *da2 = *j;
1760 if ( (da2->item == item) &&
1761 (da2->point_type == point_type) &&
1762 (point_i == -1 || da2->point_i == point_i) && // -1 means this does not matter
1763 (da2->fill_or_stroke == fill_or_stroke)) {
1764 return (dragger);
1765 }
1766 }
1767 }
1768 return nullptr;
1769}
1770
1771
1772void GrDragger::moveOtherToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr)
1773{
1774 GrDragger *d = this->parent->getDraggerFor(item, point_type, point_i, fill_or_stroke);
1775 if (d && d != this) {
1776 d->moveThisToDraggable(item, point_type, point_i, fill_or_stroke, write_repr);
1777 }
1778}
1779
1784 GrDraggable *draggable = (GrDraggable *) this->draggables[0];
1785 if (draggable) {
1786
1787 // If corner, we already found it!
1788 if (draggable->point_type == POINT_MG_CORNER) {
1789 return this;
1790 }
1791
1792 // The mapping between handles and corners is complex... so find corner by bruit force.
1793 SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke);
1794 auto mg = cast<SPMeshGradient>(gradient);
1795 if (mg) {
1796 std::vector< std::vector< SPMeshNode* > > nodes = mg->array.nodes;
1797 for (guint i = 0; i < nodes.size(); ++i) {
1798 for (guint j = 0; j < nodes[i].size(); ++j) {
1799 if (nodes[i][j]->set && nodes[i][j]->node_type == MG_NODE_TYPE_HANDLE) {
1800 if (draggable->point_i == (gint)nodes[i][j]->draggable) {
1801
1802 if (nodes.size() > i+1 && nodes[i+1].size() > j && nodes[i+1][j]->node_type == MG_NODE_TYPE_CORNER) {
1803 return this->parent->getDraggerFor(draggable->item, POINT_MG_CORNER, nodes[i+1][j]->draggable, draggable->fill_or_stroke);
1804 }
1805
1806 if (j != 0 && nodes.size() > i && nodes[i].size() > j-1 && nodes[i][j-1]->node_type == MG_NODE_TYPE_CORNER) {
1807 return this->parent->getDraggerFor(draggable->item, POINT_MG_CORNER, nodes[i][j-1]->draggable, draggable->fill_or_stroke);
1808 }
1809
1810 if (i != 0 && nodes.size() > i-1 && nodes[i-1].size() > j && nodes[i-1][j]->node_type == MG_NODE_TYPE_CORNER) {
1811 return this->parent->getDraggerFor(draggable->item, POINT_MG_CORNER, nodes[i-1][j]->draggable, draggable->fill_or_stroke);
1812 }
1813
1814 if (nodes.size() > i && nodes[i].size() > j+1 && nodes[i][j+1]->node_type == MG_NODE_TYPE_CORNER) {
1815 return this->parent->getDraggerFor(draggable->item, POINT_MG_CORNER, nodes[i][j+1]->draggable, draggable->fill_or_stroke);
1816 }
1817 }
1818 }
1819 }
1820 }
1821 }
1822 }
1823 return nullptr;
1824}
1825
1829void GrDragger::highlightNode(SPMeshNode *node, bool highlight, Geom::Point corner_pos, int index)
1830{
1832 if (node->node_type == MG_NODE_TYPE_HANDLE) {
1833 type = POINT_MG_HANDLE;
1834 }
1835
1836 GrDraggable *draggable = (GrDraggable *) this->draggables[0];
1837 GrDragger *d = this->parent->getDraggerFor(draggable->item, type, node->draggable, draggable->fill_or_stroke);
1838 if (d && node->draggable < G_MAXUINT) {
1839 Geom::Point end = d->knot->pos;
1840 Geom::Ray ray = Geom::Ray(corner_pos, end);
1841 if (d->knot->desktop->is_yaxisdown()) {
1842 end *= Geom::Scale(1, -1);
1843 corner_pos *= Geom::Scale(1, -1);
1844 ray.setPoints(corner_pos, end);
1845 }
1846 double angl = ray.angle();
1847
1848 SPKnot *knot = d->knot;
1849 if (type == POINT_MG_HANDLE) {
1850 if (highlight) {
1851 knot->selectKnot(true);
1852 } else {
1853 knot->selectKnot(false);
1854 }
1855 } else {
1856 //Code for tensors
1857 return;
1858 }
1859
1860 knot->setAngle(angl);
1861 knot->updateCtrl();
1862 d->updateKnotShape();
1863 }
1864}
1865
1869void GrDragger::highlightCorner(bool highlight)
1870{
1871 // Must be a mesh gradient
1872 GrDraggable *draggable = (GrDraggable *) this->draggables[0];
1873 if (draggable && draggable->point_type == POINT_MG_CORNER) {
1874 SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke);
1875 if (is<SPMeshGradient>( gradient )) {
1876 Geom::Point corner_point = this->point;
1877 gint corner = draggable->point_i;
1878 auto mg = cast<SPMeshGradient>( gradient );
1879 SPMeshNodeArray mg_arr = mg->array;
1880 std::vector< std::vector< SPMeshNode* > > nodes = mg_arr.nodes;
1881 // Find number of patch rows and columns
1882 guint mrow = mg_arr.patch_rows();
1883 guint mcol = mg_arr.patch_columns();
1884 // Number of corners in a row of patches.
1885 guint ncorners = mcol + 1;
1886 // Find corner row/column
1887 guint crow = corner / ncorners;
1888 guint ccol = corner % ncorners;
1889 // Find node row/column
1890 guint nrow = crow * 3;
1891 guint ncol = ccol * 3;
1892
1893 bool patch[4];
1894 patch[0] = patch[1] = patch[2] = patch[3] = false;
1895 if (ccol > 0 && crow > 0 ) patch[0] = true;
1896 if (ccol < mcol && crow > 0 ) patch[1] = true;
1897 if (ccol < mcol && crow < mrow ) patch[2] = true;
1898 if (ccol > 0 && crow < mrow ) patch[3] = true;
1899 if (patch[0] || patch[1]) {
1900 highlightNode(nodes[nrow - 1][ncol], highlight, corner_point, 0);
1901 }
1902 if (patch[1] || patch[2]) {
1903 highlightNode(nodes[nrow][ncol + 1], highlight, corner_point, 1);
1904 }
1905 if (patch[2] || patch[3]) {
1906 highlightNode(nodes[nrow + 1][ncol], highlight, corner_point, 2);
1907 }
1908 if (patch[3] || patch[0]) {
1909 highlightNode(nodes[nrow][ncol - 1], highlight, corner_point, 3);
1910 }
1911 // Highlight tensors
1912 /*
1913 if( patch[0] ) highlightNode(nodes[nrow-1][ncol-1], highlight, corner_point, point_i);
1914 if( patch[1] ) highlightNode(nodes[nrow-1][ncol+1], highlight, corner_point, point_i);
1915 if( patch[2] ) highlightNode(nodes[nrow+1][ncol+1], highlight, corner_point, point_i);
1916 if( patch[3] ) highlightNode(nodes[nrow+1][ncol-1], highlight, corner_point, point_i);
1917 */
1918 }
1919 }
1920}
1921
1926{
1927 this->knot->selectKnot(true);
1928 highlightCorner(true);
1929}
1930
1935{
1936 this->knot->selectKnot(false);
1937 highlightCorner(false);
1938}
1939
1940bool
1942{
1943 return parent->selected.find(this) != parent->selected.end();
1944}
1945
1950{
1951 for (auto it : selected)
1952 it->deselect();
1953 selected.clear();
1954}
1955
1960{
1961 deselect_all();
1963}
1964
1969{
1970 for (auto d : this->draggers) {
1971 setSelected (d, true, true);
1972 }
1973}
1974
1978void GrDrag::selectByCoords(std::vector<Geom::Point> coords)
1979{
1980 for (auto d : this->draggers) {
1981 for (auto coord : coords) {
1982 if (Geom::L2 (d->point - coord) < 1e-4) {
1983 setSelected (d, true, true);
1984 }
1985 }
1986 }
1987}
1988
1992void GrDrag::selectByStop(SPStop *stop, bool add_to_selection, bool override )
1993{
1994 for (auto dragger : this->draggers) {
1995
1996 for (std::vector<GrDraggable *>::const_iterator j = dragger->draggables.begin(); j != dragger->draggables.end(); ++j) {
1997
1998 GrDraggable *d = *j;
1999 SPGradient *gradient = getGradient(d->item, d->fill_or_stroke);
2000 SPGradient *vector = gradient->getVector(false);
2001 SPStop *stop_i = sp_get_stop_i(vector, d->point_i);
2002
2003 if (stop_i == stop) {
2004 setSelected(dragger, add_to_selection, override);
2005 }
2006 }
2007 }
2008}
2013{
2014 for (auto d : this->draggers) {
2015 if (r.contains(d->point)) {
2016 setSelected (d, true, true);
2017 }
2018 }
2019}
2020
2027void GrDrag::setSelected(GrDragger *dragger, bool add_to_selection, bool override)
2028{
2029 GrDragger *seldragger = nullptr;
2030
2031 // Don't allow selecting a mesh handle or mesh tensor.
2032 // We might want to rethink since a dragger can have draggables of different types.
2033 if ( dragger->isA( POINT_MG_HANDLE ) || dragger->isA( POINT_MG_TENSOR ) ) return;
2034
2035 if (add_to_selection) {
2036 if (!dragger) return;
2037 if (override) {
2038 selected.insert(dragger);
2039 dragger->select();
2040 seldragger = dragger;
2041 } else { // toggle
2042 if(selected.find(dragger)!=selected.end()) {
2043 selected.erase(dragger);
2044 dragger->deselect();
2045 if (!selected.empty()) {
2046 seldragger = *(selected.begin()); // select the dragger that is first in the list
2047 }
2048 } else {
2049 selected.insert(dragger);
2050 dragger->select();
2051 seldragger = dragger;
2052 }
2053 }
2054 } else {
2055 deselect_all();
2056 if (dragger) {
2057 selected.insert(dragger);
2058 dragger->select();
2059 seldragger = dragger;
2060 }
2061 }
2062 if (seldragger) {
2064 }
2065}
2066
2072{
2073 if (selected.find(dragger) != selected.end()) {
2074 selected.erase(dragger);
2075 dragger->deselect();
2076 }
2078}
2079
2084{
2085 auto const canvas_item_color = fill_or_stroke == Inkscape::FOR_FILL ? Inkscape::CANVAS_ITEM_PRIMARY : Inkscape::CANVAS_ITEM_SECONDARY;
2086
2087 auto curve = make_canvasitem<Inkscape::CanvasItemCurve>(desktop->getCanvasControls(), p1, p2);
2088 curve->set_name("GradientLine");
2089 curve->set_stroke(canvas_item_color);
2090
2091 auto item_curve = ItemCurve();
2092 item_curve.item = item;
2093 item_curve.curve = std::move(curve);
2094 item_curve.is_fill = fill_or_stroke == Inkscape::FOR_FILL;
2095 item_curves.emplace_back(std::move(item_curve));
2096}
2097
2102 int corner0, int corner1, int handle0, int handle1, Inkscape::PaintTarget fill_or_stroke)
2103
2104{
2105 // Highlight curve if one of its draggers has a mouse over it.
2106 bool highlight = false;
2107 GrDragger* dragger0 = getDraggerFor(item, POINT_MG_CORNER, corner0, fill_or_stroke);
2108 GrDragger* dragger1 = getDraggerFor(item, POINT_MG_CORNER, corner1, fill_or_stroke);
2109 GrDragger* dragger2 = getDraggerFor(item, POINT_MG_HANDLE, handle0, fill_or_stroke);
2110 GrDragger* dragger3 = getDraggerFor(item, POINT_MG_HANDLE, handle1, fill_or_stroke);
2111 if ((dragger0->knot && (dragger0->knot->flags & SP_KNOT_MOUSEOVER)) ||
2112 (dragger1->knot && (dragger1->knot->flags & SP_KNOT_MOUSEOVER)) ||
2113 (dragger2->knot && (dragger2->knot->flags & SP_KNOT_MOUSEOVER)) ||
2114 (dragger3->knot && (dragger3->knot->flags & SP_KNOT_MOUSEOVER)) ) {
2115 highlight = true;
2116 }
2117
2118 auto const canvas_item_color = (fill_or_stroke == Inkscape::FOR_FILL) ^ highlight ? Inkscape::CANVAS_ITEM_PRIMARY : Inkscape::CANVAS_ITEM_SECONDARY;
2119
2120 auto curve = make_canvasitem<Inkscape::CanvasItemCurve>(desktop->getCanvasControls(), p0, p1, p2, p3);
2121 curve->set_name("GradientCurve");
2122 curve->set_stroke(canvas_item_color);
2123
2124 auto item_curve = ItemCurve();
2125 item_curve.item = item;
2126 item_curve.curve = std::move(curve);
2127 item_curve.is_fill = fill_or_stroke == Inkscape::FOR_FILL;
2128 item_curve.corner0 = corner0;
2129 item_curve.corner1 = corner1;
2130 item_curves.emplace_back(std::move(item_curve));
2131}
2132
2138{
2139 Geom::Point p = getGradientCoords(draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
2140
2141 for (auto dragger : this->draggers) {
2142 if (dragger->mayMerge (draggable) && Geom::L2 (dragger->point - p) < MERGE_DIST) {
2143 // distance is small, merge this draggable into dragger, no need to create new dragger
2144 dragger->addDraggable (draggable);
2145 dragger->updateKnotShape();
2146 return dragger;
2147 }
2148 }
2149
2150 GrDragger *new_dragger = new GrDragger(this, p, draggable);
2151 // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
2152 this->draggers.push_back(new_dragger);
2153 return new_dragger;
2154}
2155
2160{
2161 rg->ensureVector();
2162 addDragger (new GrDraggable (item, POINT_RG_CENTER, 0, fill_or_stroke));
2163 guint num = rg->vector.stops.size();
2164 if (num > 2) {
2165 for ( guint i = 1; i < num - 1; i++ ) {
2166 addDragger (new GrDraggable (item, POINT_RG_MID1, i, fill_or_stroke));
2167 }
2168 }
2169 addDragger (new GrDraggable (item, POINT_RG_R1, num-1, fill_or_stroke));
2170 if (num > 2) {
2171 for ( guint i = 1; i < num - 1; i++ ) {
2172 addDragger (new GrDraggable (item, POINT_RG_MID2, i, fill_or_stroke));
2173 }
2174 }
2175 addDragger (new GrDraggable (item, POINT_RG_R2, num - 1, fill_or_stroke));
2176 addDragger (new GrDraggable (item, POINT_RG_FOCUS, 0, fill_or_stroke));
2177}
2178
2183{
2184 lg->ensureVector();
2185 addDragger(new GrDraggable (item, POINT_LG_BEGIN, 0, fill_or_stroke));
2186 guint num = lg->vector.stops.size();
2187 if (num > 2) {
2188 for ( guint i = 1; i < num - 1; i++ ) {
2189 addDragger(new GrDraggable (item, POINT_LG_MID, i, fill_or_stroke));
2190 }
2191 }
2192 addDragger(new GrDraggable (item, POINT_LG_END, num - 1, fill_or_stroke));
2193}
2194
2199{
2200 mg->ensureArray();
2201 std::vector< std::vector< SPMeshNode* > > nodes = mg->array.nodes;
2202
2203 // Show/hide mesh on fill/stroke. This doesn't work at the moment... and prevents node color updating.
2204
2206 bool show_handles = (prefs->getBool("/tools/mesh/show_handles", true));
2207 bool edit_fill = (prefs->getBool("/tools/mesh/edit_fill", true));
2208 bool edit_stroke = (prefs->getBool("/tools/mesh/edit_stroke", true));
2209
2210 // Make sure we have at least one patch defined.
2211 if( mg->array.patch_rows() == 0 || mg->array.patch_columns() == 0 ) {
2212
2213 std::cerr << "Empty Mesh, No Draggers to Add" << std::endl;
2214 return;
2215 }
2216
2217 guint icorner = 0;
2218 guint ihandle = 0;
2219 guint itensor = 0;
2220
2221 if( (fill_or_stroke == Inkscape::FOR_FILL && !edit_fill) ||
2222 (fill_or_stroke == Inkscape::FOR_STROKE && !edit_stroke) ) {
2223 return;
2224 }
2225
2226 for(auto & node : nodes) {
2227 for(auto & j : node) {
2228
2229 // std::cout << " Draggers: " << i << " " << j << " " << nodes[i][j]->node_type << std::endl;
2230 switch ( j->node_type ) {
2231
2233 {
2234 GrDraggable *corner = new GrDraggable (item, POINT_MG_CORNER, icorner, fill_or_stroke);
2235 addDragger ( corner );
2236 j->draggable = icorner;
2237 ++icorner;
2238 break;
2239 }
2240
2242 {
2243 GrDraggable *handle = new GrDraggable (item, POINT_MG_HANDLE, ihandle, fill_or_stroke);
2244 GrDragger* dragger = addDragger ( handle );
2245
2246 if( !show_handles || !j->set ) {
2247 dragger->knot->hide();
2248 }
2249 j->draggable = ihandle;
2250 ++ihandle;
2251 break;
2252 }
2253
2255 {
2256 GrDraggable *tensor = new GrDraggable (item, POINT_MG_TENSOR, itensor, fill_or_stroke);
2257 GrDragger* dragger = addDragger ( tensor );
2258 if( !show_handles || !j->set ) {
2259 dragger->knot->hide();
2260 }
2261 j->draggable = itensor;
2262 ++itensor;
2263 break;
2264 }
2265
2266 default:
2267 std::cerr << "Bad Mesh draggable type" << std::endl;
2268 break;
2269 }
2270 }
2271 }
2272}
2273
2279{
2280 mg->ensureArray();
2281 std::vector< std::vector< SPMeshNode* > > nodes = mg->array.nodes;
2282
2284 bool show_handles = (prefs->getBool("/tools/mesh/show_handles", true));
2285
2286 // Make sure we have at least one patch defined.
2287 if( mg->array.patch_rows() == 0 || mg->array.patch_columns() == 0 ) {
2288
2289 std::cerr << "GrDrag::refreshDraggersMesh: Empty Mesh, No Draggers to refresh!" << std::endl;
2290 return;
2291 }
2292
2293 guint ihandle = 0;
2294 guint itensor = 0;
2295
2296 for(auto & node : nodes) {
2297 for(auto & j : node) {
2298
2299 // std::cout << " Draggers: " << i << " " << j << " " << nodes[i][j]->node_type << std::endl;
2300
2301 switch ( j->node_type ) {
2302
2304 // Do nothing, corners are always shown.
2305 break;
2306
2308 {
2309 GrDragger* dragger = getDraggerFor(item, POINT_MG_HANDLE, ihandle, fill_or_stroke);
2310 if (dragger) {
2311 Geom::Point pk = getGradientCoords( item, POINT_MG_HANDLE, ihandle, fill_or_stroke);
2312 dragger->knot->moveto(pk);
2313 if( !show_handles || !j->set ) {
2314 dragger->knot->hide();
2315 } else {
2316 dragger->knot->show();
2317 }
2318 } else {
2319 // This can happen if a draggable is not visible.
2320 // std::cerr << "GrDrag::refreshDraggersMesh: No dragger!" << std::endl;
2321 }
2322 ++ihandle;
2323 break;
2324 }
2325
2327 {
2328 GrDragger* dragger = getDraggerFor(item, POINT_MG_TENSOR, itensor, fill_or_stroke);
2329 if (dragger) {
2330 Geom::Point pk = getGradientCoords( item, POINT_MG_TENSOR, itensor, fill_or_stroke);
2331 dragger->knot->moveto(pk);
2332 if( !show_handles || !j->set ) {
2333 dragger->knot->hide();
2334 } else {
2335 dragger->knot->show();
2336 }
2337 } else {
2338 // This can happen if a draggable is not visible.
2339 // std::cerr << "GrDrag::refreshDraggersMesh: No dragger!" << std::endl;
2340 }
2341 ++itensor;
2342 break;
2343 }
2344
2345 default:
2346 std::cerr << "Bad Mesh draggable type" << std::endl;
2347 break;
2348 }
2349 }
2350 }
2351}
2352
2357void GrDrag::grabKnot(GrDragger *dragger, gint x, gint y, guint32 etime)
2358{
2359 if (dragger) {
2360 dragger->knot->startDragging(dragger->point, {x, y}, etime);
2361 }
2362}
2363
2368void GrDrag::grabKnot(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, gint x, gint y, guint32 etime)
2369{
2370 GrDragger *dragger = getDraggerFor(item, point_type, point_i, fill_or_stroke);
2371 if (dragger) {
2372 dragger->knot->startDragging(dragger->point, {x, y}, etime);
2373 }
2374}
2375
2381{
2382 selected.clear();
2383 // delete old draggers
2384 for (auto dragger : this->draggers) {
2385 delete dragger;
2386 }
2387 this->draggers.clear();
2388
2389 g_return_if_fail(this->selection != nullptr);
2390 auto list = this->selection->items();
2391 for (auto i = list.begin(); i != list.end(); ++i) {
2392 SPItem *item = *i;
2393 SPStyle *style = item->style;
2394
2395 if (style && (style->fill.isPaintserver())) {
2396 SPPaintServer *server = style->getFillPaintServer();
2397 if (auto gradient = cast<SPGradient>(server)) {
2398 if (gradient->isSolid() || (gradient->getVector() && gradient->getVector()->isSolid())) {
2399 // Suppress "gradientness" of solid paint
2400 } else if (is<SPLinearGradient>(server)) {
2401 addDraggersLinear( cast<SPLinearGradient>(server), item, Inkscape::FOR_FILL );
2402 } else if (is<SPRadialGradient>(server)) {
2403 addDraggersRadial( cast<SPRadialGradient>(server), item, Inkscape::FOR_FILL );
2404 } else if (is<SPMeshGradient>(server)) {
2405 addDraggersMesh( cast<SPMeshGradient>(server), item, Inkscape::FOR_FILL );
2406 }
2407 }
2408 }
2409
2410 if (style && (style->stroke.isPaintserver())) {
2411 SPPaintServer *server = style->getStrokePaintServer();
2412 if (auto gradient = cast<SPGradient>(server)) {
2413 if (gradient->isSolid() || (gradient->getVector() && gradient->getVector()->isSolid())) {
2414 // Suppress "gradientness" of solid paint
2415 } else if (is<SPLinearGradient>(server)) {
2416 addDraggersLinear( cast<SPLinearGradient>(server), item, Inkscape::FOR_STROKE );
2417 } else if (is<SPRadialGradient>(server)) {
2418 addDraggersRadial( cast<SPRadialGradient>(server), item, Inkscape::FOR_STROKE );
2419 } else if (is<SPMeshGradient>(server)) {
2420 addDraggersMesh( cast<SPMeshGradient>(server), item, Inkscape::FOR_STROKE );
2421 }
2422 }
2423 }
2424 }
2425}
2426
2427
2434{
2435
2436 g_return_if_fail(this->selection != nullptr);
2437 auto list = this->selection->items();
2438 for (auto i = list.begin(); i != list.end(); ++i) {
2439 SPItem *item = *i;
2440 SPStyle *style = item->style;
2441
2442 if (style && (style->fill.isPaintserver())) {
2443 SPPaintServer *server = style->getFillPaintServer();
2444 if ( server && is<SPGradient>( server ) ) {
2445 if ( is<SPMeshGradient>(server) ) {
2446 refreshDraggersMesh( cast<SPMeshGradient>(server), item, Inkscape::FOR_FILL );
2447 }
2448 }
2449 }
2450
2451 if (style && (style->stroke.isPaintserver())) {
2452 SPPaintServer *server = style->getStrokePaintServer();
2453 if ( server && is<SPGradient>( server ) ) {
2454 if ( is<SPMeshGradient>(server) ) {
2455 refreshDraggersMesh( cast<SPMeshGradient>(server), item, Inkscape::FOR_STROKE );
2456 }
2457 }
2458 }
2459 }
2460}
2461
2462
2467{
2468 static bool mouse_out = false;
2469 // added knot mouse out for future use
2470 for (auto d : this->draggers) {
2471 if (d->knot && (d->knot->flags & SP_KNOT_MOUSEOVER)) {
2472 mouse_out = true;
2473 updateLines();
2474 return true;
2475 }
2476 }
2477 if(mouse_out == true){
2478 updateLines();
2479 mouse_out = false;
2480 }
2481 return false;
2482}
2483
2489{
2490 item_curves.clear();
2491
2492 g_return_if_fail(this->selection != nullptr);
2493
2494 auto list = this->selection->items();
2495 for (auto i = list.begin(); i != list.end(); ++i) {
2496 SPItem *item = *i;
2497
2498 SPStyle *style = item->style;
2499
2500 if (style && (style->fill.isPaintserver())) {
2502 if (auto gradient = cast<SPGradient>(server)) {
2503 if (gradient->isSolid() || (gradient->getVector() && gradient->getVector()->isSolid())) {
2504 // Suppress "gradientness" of solid paint
2505 } else if (is<SPLinearGradient>(server)) {
2507 } else if (is<SPRadialGradient>(server)) {
2511 } else if (is<SPMeshGradient>(server)) {
2513 bool edit_fill = (prefs->getBool("/tools/mesh/edit_fill", true));
2514
2515 auto mg = cast<SPMeshGradient>(server);
2516
2517 if (edit_fill) {
2518 guint rows = mg->array.patch_rows();
2519 guint columns = mg->array.patch_columns();
2520 for ( guint i = 0; i < rows; ++i ) {
2521 for ( guint j = 0; j < columns; ++j ) {
2522
2523 std::vector<Geom::Point> h;
2524
2525 SPMeshPatchI patch( &(mg->array.nodes), i, j );
2526
2527 // clockwise around patch, used to find corner dragger
2528 int corner0 = i * (columns + 1) + j;
2529 int corner1 = corner0 + 1;
2530 int corner2 = corner1 + columns + 1;
2531 int corner3 = corner2 - 1;
2532 // clockwise around patch, used to find handle dragger
2533 int handle0 = 2*j + i*(2+4*columns);
2534 int handle1 = handle0 + 1;
2535 int handle2 = j + i*(2+4*columns) + 2*columns + 1;
2536 int handle3 = j + i*(2+4*columns) + 3*columns + 2;
2537 int handle4 = handle1 + (2+4*columns);
2538 int handle5 = handle0 + (2+4*columns);
2539 int handle6 = handle3 - 1;
2540 int handle7 = handle2 - 1;
2541
2542 // Top line
2543 h = patch.getPointsForSide( 0 );
2544 for( guint p = 0; p < 4; ++p ) {
2545 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2546 }
2547 addCurve (item, h[0], h[1], h[2], h[3], corner0, corner1, handle0, handle1, Inkscape::FOR_FILL );
2548
2549 // Right line
2550 if( j == columns - 1 ) {
2551 h = patch.getPointsForSide( 1 );
2552 for( guint p = 0; p < 4; ++p ) {
2553 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2554 }
2555 addCurve (item, h[0], h[1], h[2], h[3], corner1, corner2, handle2, handle3, Inkscape::FOR_FILL );
2556 }
2557
2558 // Bottom line
2559 if( i == rows - 1 ) {
2560 h = patch.getPointsForSide( 2 );
2561 for( guint p = 0; p < 4; ++p ) {
2562 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2563 }
2564 addCurve (item, h[0], h[1], h[2], h[3], corner2, corner3, handle4, handle5, Inkscape::FOR_FILL );
2565 }
2566
2567 // Left line
2568 h = patch.getPointsForSide( 3 );
2569 for( guint p = 0; p < 4; ++p ) {
2570 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2571 }
2572 addCurve (item, h[0], h[1], h[2], h[3], corner3, corner0, handle6, handle7, Inkscape::FOR_FILL );
2573 }
2574 }
2575 }
2576 }
2577 }
2578 }
2579
2580 if (style && (style->stroke.isPaintserver())) {
2582 if (auto gradient = cast<SPGradient>(server)) {
2583 if (gradient->isSolid() || (gradient->getVector() && gradient->getVector()->isSolid())) {
2584 // Suppress "gradientness" of solid paint
2585 } else if (is<SPLinearGradient>(server)) {
2587 } else if (is<SPRadialGradient>(server)) {
2591 } else if (is<SPMeshGradient>(server)) {
2593 bool edit_stroke = (prefs->getBool("/tools/mesh/edit_stroke", true));
2594
2595 if (edit_stroke) {
2596
2597 // MESH FIXME: TURN ROUTINE INTO FUNCTION AND CALL FOR BOTH FILL AND STROKE.
2598 auto mg = cast<SPMeshGradient>(server);
2599
2600 guint rows = mg->array.patch_rows();
2601 guint columns = mg->array.patch_columns();
2602 for ( guint i = 0; i < rows; ++i ) {
2603 for ( guint j = 0; j < columns; ++j ) {
2604
2605 std::vector<Geom::Point> h;
2606
2607 SPMeshPatchI patch( &(mg->array.nodes), i, j );
2608
2609 // clockwise around patch, used to find corner dragger
2610 int corner0 = i * (columns + 1) + j;
2611 int corner1 = corner0 + 1;
2612 int corner2 = corner1 + columns + 1;
2613 int corner3 = corner2 - 1;
2614 // clockwise around patch, used to find handle dragger
2615 int handle0 = 2*j + i*(2+4*columns);
2616 int handle1 = handle0 + 1;
2617 int handle2 = j + i*(2+4*columns) + 2*columns + 1;
2618 int handle3 = j + i*(2+4*columns) + 3*columns + 2;
2619 int handle4 = handle1 + (2+4*columns);
2620 int handle5 = handle0 + (2+4*columns);
2621 int handle6 = handle3 - 1;
2622 int handle7 = handle2 - 1;
2623
2624 // Top line
2625 h = patch.getPointsForSide( 0 );
2626 for( guint p = 0; p < 4; ++p ) {
2627 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2628 }
2629 addCurve (item, h[0], h[1], h[2], h[3], corner0, corner1, handle0, handle1, Inkscape::FOR_STROKE);
2630
2631 // Right line
2632 if( j == columns - 1 ) {
2633 h = patch.getPointsForSide( 1 );
2634 for( guint p = 0; p < 4; ++p ) {
2635 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2636 }
2637 addCurve (item, h[0], h[1], h[2], h[3], corner1, corner2, handle2, handle3, Inkscape::FOR_STROKE);
2638 }
2639
2640 // Bottom line
2641 if( i == rows - 1 ) {
2642 h = patch.getPointsForSide( 2 );
2643 for( guint p = 0; p < 4; ++p ) {
2644 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2645 }
2646 addCurve (item, h[0], h[1], h[2], h[3], corner2, corner3, handle4, handle5, Inkscape::FOR_STROKE);
2647 }
2648
2649 // Left line
2650 h = patch.getPointsForSide( 3 );
2651 for( guint p = 0; p < 4; ++p ) {
2652 h[p] *= Geom::Affine(mg->gradientTransform) * (Geom::Affine)item->i2dt_affine();
2653 }
2654 addCurve (item, h[0], h[1], h[2], h[3], corner3, corner0, handle6, handle7,Inkscape::FOR_STROKE);
2655 }
2656 }
2657 }
2658 }
2659 }
2660 }
2661 }
2662}
2663
2669{
2670 hor_levels.clear();
2671 vert_levels.clear();
2672
2673 g_return_if_fail (this->selection != nullptr);
2674
2675 auto list = this->selection->items();
2676 for (auto i = list.begin(); i != list.end(); ++i) {
2677 SPItem *item = *i;
2679 if (rect) {
2680 // Remember the edges of the bbox and the center axis
2681 hor_levels.push_back(rect->min()[Geom::Y]);
2682 hor_levels.push_back(rect->max()[Geom::Y]);
2683 hor_levels.push_back(rect->midpoint()[Geom::Y]);
2684 vert_levels.push_back(rect->min()[Geom::X]);
2685 vert_levels.push_back(rect->max()[Geom::X]);
2686 vert_levels.push_back(rect->midpoint()[Geom::X]);
2687 }
2688 }
2689}
2690
2692{
2693 if (selected.empty())
2694 return;
2695
2696 for(auto draggable : (*(selected.begin()))->draggables) {
2697 sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
2698 }
2699}
2700
2701void GrDrag::selected_move_nowrite(double x, double y, bool scale_radial)
2702{
2703 selected_move (x, y, false, scale_radial);
2704}
2705
2706void GrDrag::selected_move(double x, double y, bool write_repr, bool scale_radial)
2707{
2708 if (selected.empty())
2709 return;
2710
2711 bool did = false;
2712
2713 auto delta = Geom::Point(x, y);
2714
2715 auto prefs = Inkscape::Preferences::get();
2716 bool const rotated = prefs->getBool("/options/moverotated/value", true);
2717 if (rotated) {
2719 }
2720
2721 for(auto d : selected) {
2722 if (!d->isA(POINT_LG_MID) && !d->isA(POINT_RG_MID1) && !d->isA(POINT_RG_MID2)) {
2723 // if this is an endpoint,
2724
2725 // Moving an rg center moves its focus and radii as well.
2726 // therefore, if this is a focus or radius and if selection
2727 // contains the center as well, do not move this one
2728 if (d->isA(POINT_RG_R1) || d->isA(POINT_RG_R2) ||
2729 (d->isA(POINT_RG_FOCUS) && !d->isA(POINT_RG_CENTER))) {
2730 bool skip_radius_with_center = false;
2731 for(auto d_new : selected) {
2732 if (d_new->isA (( d->draggables[0])->item,
2734 0,
2735 (d->draggables[0])->fill_or_stroke)) {
2736 // FIXME: here we take into account only the first draggable!
2737 skip_radius_with_center = true;
2738 }
2739 }
2740 if (skip_radius_with_center)
2741 continue;
2742 }
2743
2744 did = true;
2745 Geom::Point p_old = d->point;
2746 d->point += delta;
2747 d->point_original = d->point;
2748 d->knot->moveto(d->point);
2749
2750 d->fireDraggables (write_repr, scale_radial);
2751 d->moveMeshHandles( p_old, MG_NODE_NO_SCALE );
2752 d->updateDependencies(write_repr);
2753 }
2754 }
2755
2756 if (write_repr && did) {
2757 // we did an undoable action
2758 DocumentUndo::maybeDone(desktop->getDocument(), "grmoveh", _("Move gradient handle(s)"), INKSCAPE_ICON("color-gradient"));
2759 return;
2760 }
2761
2762 if (!did) { // none of the end draggers are selected, so let's try to move the mids
2763
2764 GrDragger *dragger = *(selected.begin());
2765 // a midpoint dragger can (logically) only contain one GrDraggable
2766 GrDraggable *draggable = dragger->draggables[0];
2767
2768 Geom::Point begin(0,0), end(0,0);
2769 Geom::Point low_lim(0,0), high_lim(0,0);
2770
2771 SPObject *server = draggable->getServer();
2772 std::vector<GrDragger *> moving;
2773 gr_midpoint_limits(dragger, server, &begin, &end, &low_lim, &high_lim, moving);
2774
2775 Geom::LineSegment ls(low_lim, high_lim);
2776 Geom::Point p = ls.pointAt(ls.nearestTime(dragger->point + Geom::Point(x,y)));
2777 Geom::Point displacement = p - dragger->point;
2778
2779 for(auto drg : moving) {
2780 SPKnot *drgknot = drg->knot;
2781 drg->point += displacement;
2782 drgknot->moveto(drg->point);
2783 drg->fireDraggables (true);
2784 drg->updateDependencies(true);
2785 did = true;
2786 }
2787
2788 if (write_repr && did) {
2789 // we did an undoable action
2790 DocumentUndo::maybeDone(desktop->getDocument(), "grmovem", _("Move gradient mid stop(s)"), INKSCAPE_ICON("color-gradient"));
2791 }
2792 }
2793}
2794
2795void GrDrag::selected_move_screen(double x, double y)
2796{
2797 gdouble zoom = desktop->current_zoom();
2798 gdouble zx = x / zoom;
2799 gdouble zy = y / zoom;
2800
2801 selected_move (zx, zy);
2802}
2803
2810{
2811 if (mod_ctrl(event)) {
2812 return false;
2813 }
2814
2815 auto keyval = Inkscape::UI::Tools::get_latin_keyval(event);
2816 double x_dir = 0;
2817 double y_dir = 0;
2818
2819 switch (keyval) {
2820 case GDK_KEY_Left: // move handle left
2821 case GDK_KEY_KP_Left:
2822 case GDK_KEY_KP_4:
2823 x_dir = -1;
2824 break;
2825
2826 case GDK_KEY_Up: // move handle up
2827 case GDK_KEY_KP_Up:
2828 case GDK_KEY_KP_8:
2829 y_dir = 1;
2830 break;
2831
2832 case GDK_KEY_Right: // move handle right
2833 case GDK_KEY_KP_Right:
2834 case GDK_KEY_KP_6:
2835 x_dir = 1;
2836 break;
2837
2838 case GDK_KEY_Down: // move handle down
2839 case GDK_KEY_KP_Down:
2840 case GDK_KEY_KP_2:
2841 y_dir = -1;
2842 break;
2843
2844 default:
2845 return false;
2846 }
2847
2848 y_dir *= -desktop->yaxisdir();
2849
2850 gint mul = 1 + Inkscape::UI::Tools::gobble_key_events(keyval, 0); // with any mask
2851
2852 if (mod_shift(event)) {
2853 mul *= 10;
2854 }
2855
2856 if (mod_alt(event)) {
2857 selected_move_screen(mul * x_dir, mul * y_dir);
2858 } else {
2859 auto *prefs = Inkscape::Preferences::get();
2860 auto nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
2861
2862 mul *= nudge;
2863 selected_move(mul * x_dir, mul * y_dir);
2864 }
2865
2866 return true;
2867}
2868
2873{
2874 GrDragger *d = nullptr;
2875 if (selected.empty() || (++find(draggers.begin(),draggers.end(),*(selected.begin())))==draggers.end()) {
2876 if (!draggers.empty())
2877 d = draggers[0];
2878 } else {
2879 d = *(++find(draggers.begin(),draggers.end(),*(selected.begin())));
2880 }
2881 if (d)
2882 setSelected (d);
2883 return d;
2884}
2885
2890{
2891 GrDragger *d = nullptr;
2892 if (selected.empty() || draggers[0] == (*(selected.begin()))) {
2893 if (!draggers.empty())
2894 d = draggers[draggers.size()-1];
2895 } else {
2896 d = *(--find(draggers.begin(),draggers.end(),*(selected.begin())));
2897 }
2898 if (d)
2899 setSelected (d);
2900 return d;
2901}
2902
2903
2904// FIXME: i.m.o. an ugly function that I just made to work, but... aargh! (Johan)
2905void GrDrag::deleteSelected(bool just_one)
2906{
2907 if (selected.empty()) return;
2908
2909 SPDocument *document = nullptr;
2910
2911 struct StructStopInfo {
2912 SPStop * spstop;
2913 GrDraggable * draggable;
2914 SPGradient * gradient;
2915 SPGradient * vector;
2916 };
2917
2918 std::vector<SPStop *> midstoplist;// list of stops that must be deleted (will be deleted first)
2919 std::vector<StructStopInfo *> endstoplist;// list of stops that must be deleted
2920
2921 while (!selected.empty()) {
2922 GrDragger *dragger = *(selected.begin());
2923 for(auto draggable : dragger->draggables) {
2924 SPGradient *gradient = getGradient(draggable->item, draggable->fill_or_stroke);
2925 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
2926
2927 switch (draggable->point_type) {
2928 case POINT_LG_MID:
2929 case POINT_RG_MID1:
2930 case POINT_RG_MID2:
2931 {
2932 SPStop *stop = sp_get_stop_i(vector, draggable->point_i);
2933 // check if already present in list. (e.g. when both RG_MID1 and RG_MID2 were selected)
2934 bool present = false;
2935 for (auto i:midstoplist) {
2936 if ( i == stop ) {
2937 present = true;
2938 break; // no need to search further.
2939 }
2940 }
2941 if (!present)
2942 midstoplist.push_back(stop);
2943 }
2944 break;
2945 case POINT_LG_BEGIN:
2946 case POINT_LG_END:
2947 case POINT_RG_CENTER:
2948 case POINT_RG_R1:
2949 case POINT_RG_R2:
2950 {
2951 SPStop *stop = nullptr;
2952 if ( (draggable->point_type == POINT_LG_BEGIN) || (draggable->point_type == POINT_RG_CENTER) ) {
2953 stop = vector->getFirstStop();
2954 } else {
2955 stop = sp_last_stop(vector);
2956 }
2957 if (stop) {
2958 StructStopInfo *stopinfo = new StructStopInfo;
2959 stopinfo->spstop = stop;
2960 stopinfo->draggable = draggable;
2961 stopinfo->gradient = gradient;
2962 stopinfo->vector = vector;
2963 // check if already present in list. (e.g. when both R1 and R2 were selected)
2964 bool present = false;
2965 for (auto i : endstoplist) {
2966 if ( i->spstop == stopinfo->spstop ) {
2967 present = true;
2968 break; // no need to search further.
2969 }
2970 }
2971 if (!present)
2972 endstoplist.push_back(stopinfo);
2973 else
2974 delete stopinfo;
2975 }
2976 }
2977 break;
2978
2979 default:
2980 break;
2981 }
2982 }
2983 selected.erase(dragger);
2984 if ( just_one ) break; // iterate once if just_one is set.
2985 }
2986 for (auto stop:midstoplist) {
2987 document = stop->document;
2988 Inkscape::XML::Node * parent = stop->getRepr()->parent();
2989 parent->removeChild(stop->getRepr());
2990 }
2991
2992 for (auto stopinfo:endstoplist) {
2993 document = stopinfo->spstop->document;
2994
2995 // 2 is the minimum, cannot delete more than that without deleting the whole vector
2996 // cannot use vector->vector.stops.size() because the vector might be invalidated by deletion of a midstop
2997 // manually count the children, don't know if there already exists a function for this...
2998 int len = 0;
2999 for (auto& child: stopinfo->vector->children)
3000 {
3001 if ( is<SPStop>(&child) ) {
3002 len ++;
3003 }
3004 }
3005 if (len > 2)
3006 {
3007 switch (stopinfo->draggable->point_type) {
3008 case POINT_LG_BEGIN:
3009 {
3010 stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr());
3011
3012 auto lg = cast<SPLinearGradient>(stopinfo->gradient);
3013 Geom::Point oldbegin = Geom::Point (lg->x1.computed, lg->y1.computed);
3014 Geom::Point end = Geom::Point (lg->x2.computed, lg->y2.computed);
3015 SPStop *stop = stopinfo->vector->getFirstStop();
3016 gdouble offset = stop->offset;
3017 Geom::Point newbegin = oldbegin + offset * (end - oldbegin);
3018 lg->x1.computed = newbegin[Geom::X];
3019 lg->y1.computed = newbegin[Geom::Y];
3020
3021 Inkscape::XML::Node *repr = stopinfo->gradient->getRepr();
3022 repr->setAttributeSvgDouble("x1", lg->x1.computed);
3023 repr->setAttributeSvgDouble("y1", lg->y1.computed);
3024 stop->offset = 0;
3025 stop->getRepr()->setAttributeCssDouble("offset", 0);
3026
3027 // iterate through midstops to set new offset values such that they won't move on canvas.
3028 SPStop *laststop = sp_last_stop(stopinfo->vector);
3029 stop = stop->getNextStop();
3030 while ( stop != laststop ) {
3031 stop->offset = (stop->offset - offset)/(1 - offset);
3032 stop->getRepr()->setAttributeCssDouble("offset", stop->offset);
3033 stop = stop->getNextStop();
3034 }
3035 }
3036 break;
3037 case POINT_LG_END:
3038 {
3039 stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr());
3040
3041 auto lg = cast<SPLinearGradient>(stopinfo->gradient);
3042 Geom::Point begin = Geom::Point (lg->x1.computed, lg->y1.computed);
3043 Geom::Point oldend = Geom::Point (lg->x2.computed, lg->y2.computed);
3044 SPStop *laststop = sp_last_stop(stopinfo->vector);
3045 gdouble offset = laststop->offset;
3046 Geom::Point newend = begin + offset * (oldend - begin);
3047 lg->x2.computed = newend[Geom::X];
3048 lg->y2.computed = newend[Geom::Y];
3049
3050 Inkscape::XML::Node *repr = stopinfo->gradient->getRepr();
3051 repr->setAttributeSvgDouble("x2", lg->x2.computed);
3052 repr->setAttributeSvgDouble("y2", lg->y2.computed);
3053 laststop->offset = 1;
3054 laststop->getRepr()->setAttributeCssDouble("offset", 1);
3055
3056 // iterate through midstops to set new offset values such that they won't move on canvas.
3057 SPStop *stop = stopinfo->vector->getFirstStop();
3058 stop = stop->getNextStop();
3059 while ( stop != laststop ) {
3060 stop->offset = stop->offset / offset;
3061 stop->getRepr()->setAttributeCssDouble("offset", stop->offset);
3062 stop = stop->getNextStop();
3063 }
3064 }
3065 break;
3066 case POINT_RG_CENTER:
3067 {
3068 SPStop *newfirst = stopinfo->spstop->getNextStop();
3069 if (newfirst) {
3070 newfirst->offset = 0;
3071 newfirst->getRepr()->setAttributeCssDouble("offset", 0);
3072 }
3073 stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr());
3074 }
3075 break;
3076 case POINT_RG_R1:
3077 case POINT_RG_R2:
3078 {
3079 stopinfo->vector->getRepr()->removeChild(stopinfo->spstop->getRepr());
3080
3081 auto rg = cast<SPRadialGradient>(stopinfo->gradient);
3082 double oldradius = rg->r.computed;
3083 SPStop *laststop = sp_last_stop(stopinfo->vector);
3084 gdouble offset = laststop->offset;
3085 double newradius = offset * oldradius;
3086 rg->r.computed = newradius;
3087
3088 Inkscape::XML::Node *repr = rg->getRepr();
3089 repr->setAttributeSvgDouble("r", rg->r.computed);
3090 laststop->offset = 1;
3091 laststop->getRepr()->setAttributeCssDouble("offset", 1);
3092
3093 // iterate through midstops to set new offset values such that they won't move on canvas.
3094 SPStop *stop = stopinfo->vector->getFirstStop();
3095 stop = stop->getNextStop();
3096 while ( stop != laststop ) {
3097 stop->offset = stop->offset / offset;
3098 stop->getRepr()->setAttributeCssDouble("offset", stop->offset);
3099 stop = stop->getNextStop();
3100 }
3101 }
3102 break;
3103 default:
3104 break;
3105 }
3106 }
3107 else
3108 { // delete the gradient from the object. set fill to unset FIXME: set to fill of unselected node?
3110
3111 // stopinfo->spstop is the selected stop
3112 Inkscape::XML::Node *unselectedrepr = stopinfo->vector->getRepr()->firstChild();
3113 if (unselectedrepr == stopinfo->spstop->getRepr() ) {
3114 unselectedrepr = unselectedrepr->next();
3115 }
3116
3117 if (unselectedrepr == nullptr) {
3118 if (stopinfo->draggable->fill_or_stroke == Inkscape::FOR_FILL) {
3120 } else {
3121 sp_repr_css_unset_property (css, "stroke");
3122 }
3123 } else {
3124 SPCSSAttr *stopcss = sp_repr_css_attr(unselectedrepr, "style");
3125 if (stopinfo->draggable->fill_or_stroke == Inkscape::FOR_FILL) {
3126 sp_repr_css_set_property(css, "fill", sp_repr_css_property(stopcss, "stop-color", "inkscape:unset"));
3127 sp_repr_css_set_property(css, "fill-opacity", sp_repr_css_property(stopcss, "stop-opacity", "1"));
3128 } else {
3129 sp_repr_css_set_property(css, "stroke", sp_repr_css_property(stopcss, "stop-color", "inkscape:unset"));
3130 sp_repr_css_set_property(css, "stroke-opacity", sp_repr_css_property(stopcss, "stop-opacity", "1"));
3131 }
3132 sp_repr_css_attr_unref (stopcss);
3133 }
3134
3135 sp_repr_css_change(stopinfo->draggable->item->getRepr(), css, "style");
3137 }
3138
3139 delete stopinfo;
3140 }
3141
3142 if (document) {
3143 DocumentUndo::done( document, _("Delete gradient stop(s)"), INKSCAPE_ICON("color-gradient"));
3144 }
3145}
3146
3147
3148/*
3149 Local Variables:
3150 mode:c++
3151 c-file-style:"stroustrup"
3152 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3153 indent-tabs-mode:nil
3154 fill-column:99
3155 End:
3156*/
3157// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
double distance(Shape const *s, Geom::Point const &p)
Definition Shape.cpp:2136
double scale
Definition aa.cpp:228
uint32_t Color
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord nearestTime(Point const &p, Coord from=0, Coord to=1) const override
Compute a time value at which the curve comes closest to a specified point.
Point pointAt(Coord t) const override
Evaluate the curve at a specified time value.
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
bool isFinite() const
Check whether both coordinates are finite.
Definition point.h:218
Straight ray from a specific point to infinity.
Definition ray.h:53
void setPoints(Point const &a, Point const &b)
Definition ray.h:75
Coord angle() const
Definition ray.h:73
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotate inverse() const
Definition transforms.h:209
Scaling from the origin.
Definition transforms.h:150
This is the root class of the gradient dragging machinery.
GrDragger * addDragger(GrDraggable *draggable)
If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create n...
void updateDraggers()
Regenerates the draggers list from the current selection; is called when selection is changed or modi...
void deselect_all()
Deselect all stops/draggers (private).
GrDrag(SPDesktop *desktop)
void refreshDraggers()
Refresh draggers, moving and toggling visibility as necessary.
GrDragger * select_prev()
Select the knot previous from the last selected one and deselect all other selected.
void updateLines()
Regenerates the lines list from the current selection; is called on each move of a dragger,...
Inkscape::Colors::Color getColor()
virtual ~GrDrag()
void deselectAll()
Deselect all stops/draggers (public; emits signal).
void addColorToDragger(GrDragger &dragger, const char *color)
void selectByStop(SPStop *stop, bool add_to_selection=true, bool override=true)
Select draggers by stop.
std::vector< double > vert_levels
void addLine(SPItem *item, Geom::Point p1, Geom::Point p2, Inkscape::PaintTarget fill_or_stroke)
Create a line from p1 to p2 and add it to the curves list.
bool local_change
void setDeselected(GrDragger *dragger)
Deselect a dragger.
bool dropColor(SPItem *item, gchar const *c, Geom::Point p)
void addDraggersRadial(SPRadialGradient *rg, SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Add draggers for the radial gradient rg on item.
void addCurve(SPItem *item, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, int corner0, int corner1, int handle0, int handle1, Inkscape::PaintTarget fill_or_stroke)
Create a curve from p0 to p3 and add it to the curves list.
GrDragger * select_next()
Select the knot next to the last selected one and deselect all other selected.
bool styleSet(const SPCSSAttr *css, bool switch_style)
SPStop * addStopNearPoint(SPItem *item, Geom::Point mouse_p, double tolerance)
void selected_move_nowrite(double x, double y, bool scale_radial)
void grabKnot(GrDragger *dragger, gint x, gint y, guint32 etime)
Artificially grab the knot of this dragger; used by the gradient context.
bool key_press_handler(Inkscape::KeyPressEvent const &event)
Handle arrow key events.
bool mouseOver()
Returns true if at least one of the draggers' knots has the mouse hovering above it.
void addDraggersMesh(SPMeshGradient *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Add draggers for the mesh gradient mg on item.
Inkscape::Selection * selection
void selectByCoords(std::vector< Geom::Point > coords)
Select all stops/draggers that match the coords.
Glib::ustring makeStopSafeColor(gchar const *str, bool &isNull)
sigc::connection style_query_connection
void selected_move_screen(double x, double y)
std::set< GrDragger * > selected
void dropColorOnCorrespondingRegion(const char *color, Geom::Point p)
std::vector< double > hor_levels
std::vector< ItemCurve > item_curves
void refreshDraggersMesh(SPMeshGradient *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Refresh draggers, moving and toggling visibility as necessary.
SPDesktop * desktop
sigc::connection sel_changed_connection
void deleteSelected(bool just_one=false)
void selected_reverse_vector()
sigc::connection style_set_connection
void updateLevels()
Regenerates the levels list from the current selection.
void selectAll()
Select all stops/draggers.
void selected_move(double x, double y, bool write_repr=true, bool scale_radial=false)
bool keep_selection
void setSelected(GrDragger *dragger, bool add_to_selection=false, bool override=true)
Select a dragger.
void selectRect(Geom::Rect const &r)
Select all stops/draggers that fall within the rect.
sigc::connection sel_modified_connection
std::vector< GrDragger * > draggers
GrDragger * getDraggerFor(GrDraggable *d)
Select the dragger which has the given draggable.
void addDraggersLinear(SPLinearGradient *lg, SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Add draggers for the linear gradient lg on item.
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
bool set(Color const &color)
Remove any other colors and set to just this one color.
double getAverage(Space::Component const &c) const
Get the average value for this component across all colors.
std::string toString(bool opacity=true) const
Format the color as a css string and return it.
Definition color.cpp:106
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
sigc::connection connectModifiedFirst(sigc::slot< void(Selection *, unsigned)> slot)
Similar to connectModified, but will be run first.
Definition selection.h:239
sigc::connection connectChangedFirst(sigc::slot< void(Selection *)> slot)
Similar to connectChanged, but will be run first.
Definition selection.h:188
Class to store data for points which are snap candidates, either as a source or as a target.
double getObjectTolerance() const
Class describing the result of an attempt to snap.
void setPoint(Geom::Point const &p)
Geom::Point getPoint() const
void setTarget(SnapTargetType const target)
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual Node * next()=0
Get the next sibling of this node.
virtual const AttributeVector & attributeList() const =0
Get a list of the node's attributes.
virtual Node * firstChild()=0
Get the first child of this node.
bool setAttributeCssDouble(Util::const_char_ptr key, double val)
Set a property attribute to val [slightly rounded], in the format required for CSS properties: in par...
Definition node.cpp:102
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual void removeChild(Node *child)=0
Remove a child of this node.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Inkscape::CanvasItemGroup * getCanvasControls() const
Definition desktop.h:196
SPDocument * getDocument() const
Definition desktop.h:189
SPItem * gr_item
Definition desktop.h:238
sigc::connection connectQueryStyle(F &&slot)
Definition desktop.h:269
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
unsigned gr_point_i
Definition desktop.h:240
SPNamedView * getNamedView() const
Definition desktop.h:191
GrPointType gr_point_type
Definition desktop.h:239
Inkscape::PaintTarget gr_fill_or_stroke
Definition desktop.h:241
Geom::Rotate const & current_rotation() const
Definition desktop.h:366
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
double yaxisdir() const
Definition desktop.h:426
sigc::connection connectSetStyle(F &&slot)
Definition desktop.h:265
SPDocument * doc() const
Definition desktop.h:159
void emit_gradient_stop_selected(SPStop *stop)
Definition desktop.cpp:1361
bool is_yaxisdown() const
Definition desktop.h:427
Typed SVG document implementation.
Definition document.h:101
std::vector< SPObject * > const getResourceList(char const *key)
Gradient.
Definition sp-gradient.h:86
void ensureArray()
Forces array (mesh) to be built, if not present (i.e.
SPStop * getFirstStop()
SPGradientVector vector
Linear and Radial Gradients.
Geom::Affine gradientTransform
gradientTransform attribute
Definition sp-gradient.h:99
SPGradient * getVector(bool force_private=false)
Returns private vector of given gradient (the gradient at the end of the href chain which has stops),...
SPMeshNodeArray array
Mesh Gradients.
int getStopCount() const
void ensureVector()
Forces vector to be built, if not present (i.e.
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1837
Geom::OptRect desktopVisualBounds() const
Get item's visual bbox in desktop coordinate system.
Definition sp-item.cpp:1065
char * detailedDescription() const
Returns a string suitable for status bar, formatted in pango markup language.
Definition sp-item.cpp:1209
Desktop-bound visual control object.
Definition knot.h:51
sigc::signal< void(SPKnot *, unsigned int)> ungrabbed_signal
Definition knot.h:97
sigc::signal< void(SPKnot *, Geom::Point const &, unsigned int)> moved_signal
Definition knot.h:98
sigc::signal< void(SPKnot *, unsigned int)> click_signal
Definition knot.h:93
void setPosition(Geom::Point const &p, unsigned int state)
Move knot to new position and emits "moved" signal.
Definition knot.cpp:342
unsigned int flags
Definition knot.h:63
Geom::Point pos
Our desktop coordinates.
Definition knot.h:69
sigc::signal< void(SPKnot *, unsigned int)> mousedown_signal
Definition knot.h:95
void setAngle(double i)
Definition knot.cpp:422
sigc::signal< void(SPKnot *, unsigned int)> doubleclicked_signal
Definition knot.h:94
CanvasItemPtr< Inkscape::CanvasItemCtrl > ctrl
Our CanvasItemCtrl.
Definition knot.h:60
static void unref(SPKnot *knot)
Definition knot.cpp:47
void hide()
Hide knot on its canvas.
Definition knot.cpp:327
void show()
Show knot on its canvas.
Definition knot.cpp:322
void setTip(Glib::ustring &&tip)
Definition knot.cpp:438
void updateCtrl()
Update knot's control state.
Definition knot.cpp:389
SPDesktop * desktop
Desktop we are on.
Definition knot.h:59
void moveto(Geom::Point const &p)
Move knot to new position, without emitting a MOVED signal.
Definition knot.cpp:353
void selectKnot(bool select)
Select knot.
Definition knot.cpp:103
void startDragging(Geom::Point const &p, Geom::IntPoint const &xy, uint32_t etime)
Update knot for dragging and tell canvas an item was grabbed.
Definition knot.cpp:86
Linear gradient.
Mesh gradient.
std::vector< std::vector< SPMeshNode * > > nodes
unsigned patch_columns()
Number of patch columns.
unsigned patch_rows()
Number of patch rows.
std::vector< Geom::Point > getPointsForSide(unsigned i)
Returns vector of points for a side in proper order for a patch (clockwise order).
Geom::Point getPoint(unsigned side, unsigned point)
Returns point for side in proper order for patch.
SnapManager snap_manager
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
Radial gradient.
Gradient stop.
Definition sp-stop.h:31
float offset
Definition sp-stop.h:38
SPStop * getNextStop()
Virtual write: write object attributes to repr.
Definition sp-stop.cpp:98
Inkscape::Colors::Color getColor() const
Definition sp-stop.cpp:130
SPStop * getPrevStop()
Definition sp-stop.cpp:110
An SVG style object.
Definition style.h:45
SPPaintServer * getFillPaintServer()
Definition style.h:339
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPPaintServer * getStrokePaintServer()
Definition style.h:343
T< SPAttr::FILL_OPACITY, SPIScale24 > fill_opacity
fill-opacity
Definition style.h:242
T< SPAttr::STROKE_OPACITY, SPIScale24 > stroke_opacity
stroke-opacity
Definition style.h:261
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
Inkscape::SnapPreferences & snapprefs
Definition snap.h:342
A set of colors which can be modified together used for color pickers.
TODO: insert short description here.
std::shared_ptr< Css const > css
double c[8][4]
@ QUERY_STYLE_PROPERTY_FILL
@ QUERY_STYLE_PROPERTY_MASTEROPACITY
@ QUERY_STYLE_SINGLE
@ QUERY_STYLE_NOTHING
@ QUERY_STYLE_MULTIPLE_AVERAGED
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
unsigned int guint32
SPStop * sp_get_stop_i(SPGradient *gradient, guint stop_i)
Color sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
Returns the position of point point_type of the gradient applied to item (either fill_or_stroke),...
void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop)
SPStop * sp_item_gradient_get_stop(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
SPStop * sp_last_stop(SPGradient *gradient)
SPStop * sp_gradient_add_stop_at(SPGradient *gradient, double offset)
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_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property)
Convert an item's gradient to userspace if necessary, also fork it if necessary.
void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
static void gr_knot_mousedown_handler(SPKnot *, unsigned int, gpointer data)
static void gr_knot_moved_handler(SPKnot *knot, Geom::Point const &ppointer, guint state, gpointer data)
std::unordered_map< GrPointType, char const * > const gr_knot_descr
static void gr_drag_sel_modified(Inkscape::Selection *, guint, gpointer data)
static void gr_knot_ungrabbed_handler(SPKnot *knot, unsigned int state, gpointer data)
Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other dragge...
static int gr_drag_style_query(SPStyle *style, int property, gpointer data)
When a _query_style_signal is received, check that property requests fill/stroke/opacity (otherwise s...
static void gr_drag_sel_changed(Inkscape::Selection *, gpointer data)
static void gr_knot_doubleclicked_handler(SPKnot *, guint, gpointer data)
Called when a dragger knot is doubleclicked;.
static void gr_midpoint_limits(GrDragger *dragger, SPObject *server, Geom::Point *begin, Geom::Point *end, Geom::Point *low_lim, Geom::Point *high_lim, std::vector< GrDragger * > &moving)
static void gr_knot_clicked_handler(SPKnot *, guint state, gpointer data)
Called when a dragger knot is clicked; selects the dragger or deletes it depending on the state of th...
std::unordered_map< GrPointType, Inkscape::CanvasItemCtrlType > const gr_knot_types
static void gr_knot_moved_midpoint_handler(SPKnot *, Geom::Point const &ppointer, guint state, gpointer data)
Called when a midpoint knot is dragged.
BezierCurveN< 1 > LineSegment
Line segment.
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Inkscape::XML::Node * node
@ SP_KNOT_STATE_NORMAL
Definition knot-enums.h:20
@ SP_KNOT_MOUSEOVER
Definition knot-enums.h:31
Declarations for SPKnot: Desktop-bound visual control object.
double offset
Geom::Point end
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
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.
int gobble_key_events(unsigned keyval, unsigned mask)
Definition tool-base.h:243
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
@ SNAPTARGET_CONSTRAINED_ANGLE
Definition snap-enums.h:120
@ CANVAS_ITEM_CTRL_TYPE_SIZER
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ CANVAS_ITEM_CTRL_TYPE_SHAPER
@ CANVAS_ITEM_CTRL_TYPE_MESH
@ CANVAS_ITEM_CTRL_TYPE_ROTATE
std::vector< PaintTarget > const & allPaintTargets()
Convenience function to access a common vector of all enum values.
Ocnode * child[8]
Definition quantize.cpp:33
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_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
Set a style property to "inkscape:unset".
Definition repr-css.cpp:202
bool sp_repr_css_property_is_unset(SPCSSAttr *css, gchar const *name)
Returns true if a style property is present and its value is unset.
Definition repr-css.cpp:178
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
auto len
Definition safe-printf.h:21
int num
Definition scribble.cpp:47
SPCSSAttr - interface for CSS Attributes.
GrPointType
Definition sp-gradient.h:46
@ 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_MG_CORNER
Definition sp-gradient.h:56
@ POINT_RG_CENTER
Definition sp-gradient.h:50
@ POINT_MG_TENSOR
Definition sp-gradient.h:58
@ 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_MG_HANDLE
Definition sp-gradient.h:57
@ POINT_LG_MID
Definition sp-gradient.h:49
@ POINT_RG_FOCUS
Definition sp-gradient.h:53
TODO: insert short description here.
MeshNodeOperation
@ MG_NODE_NO_SCALE
@ MG_NODE_TYPE_TENSOR
@ MG_NODE_TYPE_HANDLE
@ MG_NODE_TYPE_CORNER
TODO: insert short description here.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
TODO: insert short description here.
TODO: insert short description here.
static const Point data[]
This class represents a single draggable point of a gradient.
GrDraggable(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
GrPointType point_type
SPObject * getServer()
bool mayMerge(GrDraggable *da2)
virtual ~GrDraggable()
Inkscape::PaintTarget fill_or_stroke
SPItem * item
This class holds together a visible on-canvas knot and a list of draggables that need to be moved whe...
void updateTip()
Updates the statusbar tip of the dragger knot, based on its draggables.
std::vector< GrDraggable * > draggables
void updateDependencies(bool write_repr)
Moves all draggables that depend on this one.
void moveOtherToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr)
GrDragger * getMgCorner()
Find mesh corner corresponding to given dragger.
void highlightCorner(bool highlight)
Highlight handles for mesh corner corresponding to this dragger.
GrDragger(GrDrag *parent, Geom::Point p, GrDraggable *draggable)
void updateControlSizes()
SPKnot * knot
sigc::connection _doubleclicked_connection
void fireDraggables(bool write_repr, bool scale_radial=false, bool merging_focus=false)
Act upon all draggables of the dragger, setting them to the dragger's point.
GrDrag * parent
void addDraggable(GrDraggable *draggable)
Adds a draggable to the dragger.
sigc::connection _mousedown_connection
void highlightNode(SPMeshNode *node, bool highlight, Geom::Point corner_pos, int index)
Highlight mesh node.
void updateMidstopDependencies(GrDraggable *draggable, bool write_repr)
Moves all midstop draggables that depend on this one.
void deselect()
Draw this dragger as normal (deselected).
sigc::connection _clicked_connection
void moveMeshHandles(Geom::Point pc_old, MeshNodeOperation op)
Update mesh handles when mesh corner is moved.
bool mayMerge(GrDragger *other)
void updateKnotShape()
Adds a draggable to the dragger.
sigc::connection _moved_connection
Geom::Point point_original
bool isA(GrPointType point_type)
Checks if the dragger has a draggable with this point_type.
virtual ~GrDragger()
void select()
Draw this dragger as selected.
void moveThisToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr)
Moves this dragger to the point of the given draggable, acting upon all other draggables.
sigc::connection _ungrabbed_connection
Geom::Point point
A key has been pressed.
std::list< Inkscape::SnappedPoint > points
Definition snapper.h:26
std::vector< SPGradientStop > stops
Definition curve.h:24
SPStyle - a style object for SPItem objects.
double sp_svg_read_percentage(char const *str, double def)
int delta
int index
SPDesktop * desktop