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