2320a98de1f1dc7f4f1dc708606ac88567bd593f
1 #define __GRADIENT_DRAG_C__
3 /*
4 * On-canvas gradient dragging
5 *
6 * Authors:
7 * bulia byak <buliabyak@users.sf.net>
8 *
9 * Copyright (C) 2005 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #include <glibmm/i18n.h>
20 #include "desktop-handles.h"
21 #include "selection.h"
22 #include "desktop.h"
23 #include "desktop-style.h"
24 #include "document.h"
25 #include "display/sp-ctrlline.h"
27 #include "xml/repr.h"
29 #include "prefs-utils.h"
30 #include "sp-item.h"
31 #include "style.h"
32 #include "knot.h"
33 #include "sp-linear-gradient.h"
34 #include "sp-radial-gradient.h"
35 #include "gradient-chemistry.h"
36 #include "gradient-drag.h"
37 #include "sp-stop.h"
39 #define GR_KNOT_COLOR_NORMAL 0xffffff00
40 #define GR_KNOT_COLOR_SELECTED 0x0000ff00
42 #define GR_LINE_COLOR_FILL 0x0000ff7f
43 #define GR_LINE_COLOR_STROKE 0x9999007f
45 // screen pixels between knots when they snap:
46 #define SNAP_DIST 5
48 // absolute distance between gradient points for them to become a single dragger when the drag is created:
49 #define MERGE_DIST 0.1
51 // knot shapes corresponding to GrPointType enum
52 SPKnotShapeType gr_knot_shapes [] = {
53 SP_KNOT_SHAPE_SQUARE, //POINT_LG_BEGIN
54 SP_KNOT_SHAPE_CIRCLE, //POINT_LG_END
55 SP_KNOT_SHAPE_DIAMOND, //POINT_LG_MID
56 SP_KNOT_SHAPE_DIAMOND,
57 SP_KNOT_SHAPE_CIRCLE,
58 SP_KNOT_SHAPE_CIRCLE,
59 SP_KNOT_SHAPE_CROSS, // POINT_RG_FOCUS
60 SP_KNOT_SHAPE_DIAMOND, //POINT_RG_MID1
61 SP_KNOT_SHAPE_DIAMOND //POINT_RG_MID2
62 };
64 const gchar *gr_knot_descr [] = {
65 N_("Linear gradient <b>start</b>"), //POINT_LG_BEGIN
66 N_("Linear gradient <b>end</b>"),
67 N_("Linear gradient <b>midstop</b>"),
68 N_("Radial gradient <b>center</b>"),
69 N_("Radial gradient <b>radius</b>"),
70 N_("Radial gradient <b>radius</b>"),
71 N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
72 N_("Linear gradient <b>midstop</b>"),
73 N_("Linear gradient <b>midstop</b>")
74 };
76 static void
77 gr_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
78 {
79 GrDrag *drag = (GrDrag *) data;
80 drag->updateDraggers ();
81 drag->updateLines ();
82 drag->updateLevels ();
83 }
85 static void
86 gr_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
87 {
88 GrDrag *drag = (GrDrag *) data;
89 if (drag->local_change) {
90 drag->local_change = false;
91 } else {
92 drag->updateDraggers ();
93 }
94 drag->updateLines ();
95 drag->updateLevels ();
96 }
98 /**
99 When a _query_style_signal is received, check that \a property requests fill/stroke (otherwise
100 skip), and fill the \a style with the averaged color of all draggables of the selected dragger, if
101 any.
102 */
103 int
104 gr_drag_style_query (SPStyle *style, int property, gpointer data)
105 {
106 GrDrag *drag = (GrDrag *) data;
108 if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE) {
109 return QUERY_STYLE_NOTHING;
110 }
112 if (!drag->selected) {
113 return QUERY_STYLE_NOTHING;
114 } else {
115 int ret = QUERY_STYLE_NOTHING;
117 float cf[4];
118 cf[0] = cf[1] = cf[2] = cf[3] = 0;
120 int count = 0;
122 for (GSList const* i = ((GrDragger*)drag->selected->data)->draggables; i != NULL; i = i->next) { // for all draggables of dragger
123 GrDraggable *draggable = (GrDraggable *) i->data;
125 if (ret == QUERY_STYLE_NOTHING) {
126 ret = QUERY_STYLE_SINGLE;
127 } else if (ret == QUERY_STYLE_SINGLE) {
128 ret = QUERY_STYLE_MULTIPLE_AVERAGED;
129 }
131 guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
132 cf[0] += SP_RGBA32_R_F (c);
133 cf[1] += SP_RGBA32_G_F (c);
134 cf[2] += SP_RGBA32_B_F (c);
135 cf[3] += SP_RGBA32_A_F (c);
137 count ++;
138 }
140 if (count) {
141 cf[0] /= count;
142 cf[1] /= count;
143 cf[2] /= count;
144 cf[3] /= count;
146 // set both fill and stroke with our stop-color and stop-opacity
147 sp_color_set_rgb_float((SPColor *) &style->fill.value.color, cf[0], cf[1], cf[2]);
148 style->fill.set = TRUE;
149 style->fill.type = SP_PAINT_TYPE_COLOR;
150 sp_color_set_rgb_float((SPColor *) &style->stroke.value.color, cf[0], cf[1], cf[2]);
151 style->stroke.set = TRUE;
152 style->stroke.type = SP_PAINT_TYPE_COLOR;
154 style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
155 style->fill_opacity.set = TRUE;
156 style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
157 style->stroke_opacity.set = TRUE;
158 }
160 return ret;
161 }
162 }
164 bool
165 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
166 {
167 GrDrag *drag = (GrDrag *) data;
169 if (!drag->selected)
170 return false;
172 SPCSSAttr *stop = sp_repr_css_attr_new ();
174 // See if the css contains interesting properties, and if so, translate them into the format
175 // acceptable for gradient stops
177 // any of color properties, in order of increasing priority:
178 if (css->attribute("flood-color"))
179 sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color"));
181 if (css->attribute("lighting-color"))
182 sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color"));
184 if (css->attribute("color"))
185 sp_repr_css_set_property (stop, "stop-color", css->attribute("color"));
187 if (css->attribute("stroke") && strcmp(css->attribute("stroke"), "none"))
188 sp_repr_css_set_property (stop, "stop-color", css->attribute("stroke"));
190 if (css->attribute("fill") && strcmp(css->attribute("fill"), "none"))
191 sp_repr_css_set_property (stop, "stop-color", css->attribute("fill"));
193 if (css->attribute("stop-color"))
194 sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color"));
196 // any of opacity properties, in order of increasing priority:
197 if (css->attribute("flood-opacity"))
198 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("flood-color"));
200 if (css->attribute("opacity")) // TODO: multiply
201 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("color"));
203 if (css->attribute("stroke-opacity")) // TODO: multiply
204 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stroke-opacity"));
206 if (css->attribute("fill-opacity")) // TODO: multiply
207 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("fill-opacity"));
209 if ((css->attribute("fill") && !strcmp(css->attribute("fill"), "none")) ||
210 (css->attribute("stroke") && !strcmp(css->attribute("stroke"), "none")))
211 sp_repr_css_set_property (stop, "stop-opacity", "0"); // if set to none, don't change color, set opacity to 0
213 if (css->attribute("stop-opacity"))
214 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stop-opacity"));
216 if (!stop->attributeList()) { // nothing for us here, pass it on
217 sp_repr_css_attr_unref(stop);
218 return false;
219 }
221 for (GList const* sel = drag->selected; sel != NULL; sel = sel->next) { // for all selected draggers
222 GrDragger* dragger = (GrDragger*) sel->data;
223 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
224 GrDraggable *draggable = (GrDraggable *) i->data;
226 drag->local_change = true;
227 sp_item_gradient_stop_set_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke, stop);
228 }
229 }
231 //sp_repr_css_print(stop);
232 sp_repr_css_attr_unref(stop);
233 return true;
234 }
236 GrDrag::GrDrag(SPDesktop *desktop) {
238 this->desktop = desktop;
240 this->selection = sp_desktop_selection(desktop);
242 this->draggers = NULL;
243 this->lines = NULL;
244 this->selected = NULL;
246 this->hor_levels.clear();
247 this->vert_levels.clear();
249 this->local_change = false;
251 this->sel_changed_connection = this->selection->connectChanged(
252 sigc::bind (
253 sigc::ptr_fun(&gr_drag_sel_changed),
254 (gpointer)this )
256 );
257 this->sel_modified_connection = this->selection->connectModified(
258 sigc::bind(
259 sigc::ptr_fun(&gr_drag_sel_modified),
260 (gpointer)this )
261 );
263 this->style_set_connection = this->desktop->connectSetStyle(
264 sigc::bind(
265 sigc::ptr_fun(&gr_drag_style_set),
266 (gpointer)this )
267 );
269 this->style_query_connection = this->desktop->connectQueryStyle(
270 sigc::bind(
271 sigc::ptr_fun(&gr_drag_style_query),
272 (gpointer)this )
273 );
275 this->updateDraggers ();
276 this->updateLines ();
277 this->updateLevels ();
279 if (desktop->gr_item) {
280 this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_type, desktop->gr_point_i, desktop->gr_fill_or_stroke));
281 }
282 }
284 GrDrag::~GrDrag()
285 {
286 this->sel_changed_connection.disconnect();
287 this->sel_modified_connection.disconnect();
288 this->style_set_connection.disconnect();
289 this->style_query_connection.disconnect();
291 if (this->selected) {
292 GrDraggable *draggable = (GrDraggable *) ((GrDragger*)this->selected->data)->draggables->data;
293 desktop->gr_item = draggable->item;
294 desktop->gr_point_type = draggable->point_type;
295 desktop->gr_point_i = draggable->point_i;
296 desktop->gr_fill_or_stroke = draggable->fill_or_stroke;
297 } else {
298 desktop->gr_item = NULL;
299 desktop->gr_point_type = 0;
300 desktop->gr_point_i = 0;
301 desktop->gr_fill_or_stroke = true;
302 }
304 deselect_all();
305 for (GList *l = this->draggers; l != NULL; l = l->next) {
306 delete ((GrDragger *) l->data);
307 }
308 g_list_free (this->draggers);
309 this->draggers = NULL;
310 this->selected = NULL;
312 for (GSList *l = this->lines; l != NULL; l = l->next) {
313 gtk_object_destroy( GTK_OBJECT (l->data));
314 }
315 g_slist_free (this->lines);
316 this->lines = NULL;
317 }
319 GrDraggable::GrDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
320 {
321 this->item = item;
322 this->point_type = point_type;
323 this->point_i = point_i;
324 this->fill_or_stroke = fill_or_stroke;
326 g_object_ref (G_OBJECT (this->item));
327 }
329 GrDraggable::~GrDraggable ()
330 {
331 g_object_unref (G_OBJECT (this->item));
332 }
334 // FIXME: make global function in libnr or somewhere.
335 static NR::Point *
336 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
337 {
338 double r = NR::L2 (p - o);
339 if (r < 1e-3)
340 return NULL;
341 double angle = NR::atan2 (p - o);
342 // snap angle to snaps increments, starting from initial:
343 double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap;
344 // calculate the new position and subtract p to get the vector:
345 return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p);
346 }
348 // FIXME: make global function in libnr or somewhere.
349 static NR::Point
350 snap_vector_midpoint (NR::Point p, NR::Point begin, NR::Point end, double snap)
351 {
352 double length = NR::L2(end - begin);
353 NR::Point be = (end - begin) / length;
354 double r = NR::dot(p - begin, be);
356 if (r < 0.0) return begin;
357 if (r > length) return end;
359 double snapdist = length * snap;
360 double r_snapped = (snap==0) ? r : floor(r/(snapdist + 0.5)) * snapdist;
362 return (begin + r_snapped * be);
363 }
365 static void
366 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
367 {
368 GrDragger *dragger = (GrDragger *) data;
369 GrDrag *drag = dragger->parent;
371 NR::Point p = *ppointer;
373 // FIXME: take from prefs
374 double snap_dist = SNAP_DIST / dragger->parent->desktop->current_zoom();
376 if (state & GDK_SHIFT_MASK) {
377 // with Shift; unsnap if we carry more than one draggable
378 if (dragger->draggables && dragger->draggables->next) {
379 // create a new dragger
380 GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, NULL);
381 dragger->parent->draggers = g_list_prepend (dragger->parent->draggers, dr_new);
382 // relink to it all but the first draggable in the list
383 for (GSList const* i = dragger->draggables->next; i != NULL; i = i->next) {
384 GrDraggable *draggable = (GrDraggable *) i->data;
385 dr_new->addDraggable (draggable);
386 }
387 dr_new->updateKnotShape();
388 g_slist_free (dragger->draggables->next);
389 dragger->draggables->next = NULL;
390 dragger->updateKnotShape();
391 dragger->updateTip();
392 }
393 } else if (!(state & GDK_CONTROL_MASK)) {
394 // without Shift or Ctrl; see if we need to snap to another dragger
395 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
396 GrDragger *d_new = (GrDragger *) di->data;
397 if (dragger->mayMerge(d_new) && NR::L2 (d_new->point - p) < snap_dist) {
399 // Merge draggers:
400 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
401 GrDraggable *draggable = (GrDraggable *) i->data;
402 // copy draggable to d_new:
403 GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
404 d_new->addDraggable (da_new);
405 }
407 // unlink and delete this dragger
408 dragger->parent->draggers = g_list_remove (dragger->parent->draggers, dragger);
409 delete dragger;
411 // update the new merged dragger
412 d_new->fireDraggables(true, false, true);
413 d_new->parent->updateLines();
414 d_new->parent->setSelected (d_new);
415 d_new->updateKnotShape ();
416 d_new->updateTip ();
417 d_new->updateDependencies(true);
418 sp_document_done (sp_desktop_document (d_new->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
419 _("Merge gradient handles"));
420 return;
421 }
422 }
423 }
426 if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) {
427 // See if we need to snap to any of the levels
428 for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) {
429 if (fabs(p[NR::Y] - dragger->parent->hor_levels[i]) < snap_dist) {
430 p[NR::Y] = dragger->parent->hor_levels[i];
431 sp_knot_moveto (knot, &p);
432 }
433 }
434 for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) {
435 if (fabs(p[NR::X] - dragger->parent->vert_levels[i]) < snap_dist) {
436 p[NR::X] = dragger->parent->vert_levels[i];
437 sp_knot_moveto (knot, &p);
438 }
439 }
440 }
442 if (state & GDK_CONTROL_MASK) {
443 unsigned snaps = abs(prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12));
444 /* 0 means no snapping. */
446 // This list will store snap vectors from all draggables of dragger
447 GSList *snap_vectors = NULL;
449 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) {
450 GrDraggable *draggable = (GrDraggable *) i->data;
452 NR::Point *dr_snap = NULL;
454 if (draggable->point_type == POINT_LG_BEGIN || draggable->point_type == POINT_LG_END) {
455 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
456 GrDragger *d_new = (GrDragger *) di->data;
457 if (d_new == dragger)
458 continue;
459 if (d_new->isA (draggable->item,
460 draggable->point_type == POINT_LG_BEGIN? POINT_LG_END : POINT_LG_BEGIN,
461 draggable->point_i,
462 draggable->fill_or_stroke)) {
463 // found the other end of the linear gradient;
464 if (state & GDK_SHIFT_MASK) {
465 // moving linear around center
466 NR::Point center = NR::Point (0.5*(d_new->point + dragger->point));
467 dr_snap = ¢er;
468 } else {
469 // moving linear around the other end
470 dr_snap = &d_new->point;
471 }
472 }
473 }
474 } else if (draggable->point_type == POINT_RG_R1 || draggable->point_type == POINT_RG_R2 || draggable->point_type == POINT_RG_FOCUS) {
475 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
476 GrDragger *d_new = (GrDragger *) di->data;
477 if (d_new == dragger)
478 continue;
479 if (d_new->isA (draggable->item,
480 POINT_RG_CENTER,
481 draggable->point_i,
482 draggable->fill_or_stroke)) {
483 // found the center of the radial gradient;
484 dr_snap = &(d_new->point);
485 }
486 }
487 } else if (draggable->point_type == POINT_RG_CENTER) {
488 // radial center snaps to hor/vert relative to its original position
489 dr_snap = &(dragger->point_original);
490 }
492 NR::Point *snap_vector = NULL;
493 if (dr_snap) {
494 if (state & GDK_MOD1_MASK) {
495 // with Alt, snap to the original angle and its perpendiculars
496 snap_vector = get_snap_vector (p, *dr_snap, M_PI/2, NR::atan2 (dragger->point_original - *dr_snap));
497 } else {
498 // with Ctrl, snap to M_PI/snaps
499 snap_vector = get_snap_vector (p, *dr_snap, M_PI/snaps, 0);
500 }
501 }
502 if (snap_vector) {
503 snap_vectors = g_slist_prepend (snap_vectors, snap_vector);
504 }
505 }
507 // Move by the smallest of snap vectors:
508 NR::Point move(9999, 9999);
509 for (GSList const *i = snap_vectors; i != NULL; i = i->next) {
510 NR::Point *snap_vector = (NR::Point *) i->data;
511 if (NR::L2(*snap_vector) < NR::L2(move))
512 move = *snap_vector;
513 }
514 if (move[NR::X] < 9999) {
515 p += move;
516 sp_knot_moveto (knot, &p);
517 }
519 g_slist_free(snap_vectors);
520 }
522 dragger->point = p;
524 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
525 dragger->fireDraggables (false, true);
526 } else {
527 dragger->fireDraggables (false);
528 }
530 dragger->updateDependencies(false);
532 drag->keep_selection = (bool) g_list_find(drag->selected, dragger);
533 }
536 /**
537 Called when a midpoint knot is dragged.
538 */
539 static void
540 gr_knot_moved_midpoint_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
541 {
542 GrDragger *dragger = (GrDragger *) data;
543 GrDrag *drag = dragger->parent;
544 // a midpoint dragger can (logically) only contain one GrDraggable
545 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
547 // FIXME: take from prefs
548 double snap_fraction = 0.1;
550 NR::Point p = *ppointer;
551 NR::Point begin(0,0), end(0,0);
553 SPObject *server;
554 if (draggable->fill_or_stroke)
555 server = SP_OBJECT_STYLE_FILL_SERVER (draggable->item);
556 else
557 server = SP_OBJECT_STYLE_STROKE_SERVER (draggable->item);
560 // get begin and end points between which dragging is allowed:
561 // the draglimits are between knot(lowest_i - 1) and knot(highest_i + 1)
562 GSList *moving = NULL;
563 moving = g_slist_append(moving, dragger);
565 guint lowest_i = draggable->point_i;
566 guint highest_i = draggable->point_i;
567 GrDragger *lowest_dragger = dragger;
568 GrDragger *highest_dragger = dragger;
569 bool is_selected = g_list_find(drag->selected, dragger);
570 if (is_selected) {
571 GrDragger* d_add;
572 while ( true )
573 {
574 d_add = drag->getDraggerFor(draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
575 if ( d_add && g_list_find(drag->selected, d_add) ) {
576 lowest_i = lowest_i - 1;
577 moving = g_slist_prepend(moving, d_add);
578 lowest_dragger = d_add;
579 } else {
580 break;
581 }
582 }
584 while ( true )
585 {
586 d_add = drag->getDraggerFor(draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
587 if ( d_add && g_list_find(drag->selected, d_add) ) {
588 highest_i = highest_i + 1;
589 moving = g_slist_append(moving, d_add);
590 highest_dragger = d_add;
591 } else {
592 break;
593 }
594 }
595 }
597 if ( SP_IS_LINEARGRADIENT(server) ) {
598 GrDragger *d_temp;
599 if (lowest_i == 1) {
600 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke);
601 } else {
602 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, lowest_i - 1, draggable->fill_or_stroke);
603 }
604 if (d_temp) begin = d_temp->point;
606 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, highest_i + 1, draggable->fill_or_stroke);
607 if (d_temp == NULL) {
608 d_temp = drag->getDraggerFor (draggable->item, POINT_LG_END, 0, draggable->fill_or_stroke);
609 }
610 if (d_temp) end = d_temp->point;
611 } else if ( SP_IS_RADIALGRADIENT(server) ) {
612 GrDragger *d_temp;
613 if (lowest_i == 1) {
614 d_temp = drag->getDraggerFor (draggable->item, POINT_RG_CENTER, 0, draggable->fill_or_stroke);
615 } else {
616 d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
617 }
618 if (d_temp) begin = d_temp->point;
620 d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
621 if (d_temp == NULL) {
622 d_temp = drag->getDraggerFor (draggable->item, (draggable->point_type==POINT_RG_MID1) ? POINT_RG_R1 : POINT_RG_R2, 0, draggable->fill_or_stroke);
623 }
624 if (d_temp) end = d_temp->point;
625 }
627 NR::Point low_lim = dragger->point - (lowest_dragger->point - begin);
628 NR::Point high_lim = dragger->point - (highest_dragger->point - end);
630 if (state & GDK_CONTROL_MASK) {
631 p = snap_vector_midpoint (p, low_lim, high_lim, snap_fraction);
632 } else {
633 p = snap_vector_midpoint (p, low_lim, high_lim, 0);
634 }
635 NR::Point displacement = p - dragger->point;
637 for (GSList const* i = moving; i != NULL; i = i->next) {
638 GrDragger *drg = (GrDragger*) i->data;
639 SPKnot *drgknot = drg->knot;
640 drg->point += displacement;
641 sp_knot_moveto (drgknot, & drg->point);
642 drg->fireDraggables (false);
643 drg->updateDependencies(false);
644 }
646 drag->keep_selection = is_selected;
647 }
651 static void
652 gr_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
653 {
654 GrDragger *dragger = (GrDragger *) data;
656 sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
657 }
659 /**
660 Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
661 */
662 static void
663 gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
664 {
665 GrDragger *dragger = (GrDragger *) data;
667 sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
669 dragger->point_original = dragger->point = knot->pos;
671 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
672 dragger->fireDraggables (true, true);
673 } else {
674 dragger->fireDraggables (true);
675 }
677 // make this dragger selected
678 if (!dragger->parent->keep_selection) {
679 dragger->parent->setSelected (dragger);
680 }
681 dragger->parent->keep_selection = false;
683 dragger->updateDependencies(true);
685 // we did an undoable action
686 sp_document_done (sp_desktop_document (dragger->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
687 _("Move gradient handle"));
688 }
690 /**
691 Called when a dragger knot is clicked; selects the dragger or deletes it depending on the
692 state of the keyboard keys
693 */
694 static void
695 gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
696 {
697 GrDragger *dragger = (GrDragger *) data;
698 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
699 if (!draggable) return;
701 if ( (state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK ) ) {
702 // delete this knot from vector
703 SPGradient *gradient = sp_item_gradient (draggable->item, draggable->fill_or_stroke);
704 gradient = sp_gradient_get_vector (gradient, false);
705 if (gradient->vector.stops.size() > 2) { // 2 is the minimum
706 SPStop *stop;
707 switch (draggable->point_type) { // if we delete first or last stop, move the next/previous to the edge
708 case POINT_LG_BEGIN:
709 case POINT_RG_CENTER:
710 stop = sp_first_stop(gradient);
711 {
712 SPStop *next = sp_next_stop (stop);
713 if (next) {
714 next->offset = 0;
715 sp_repr_set_css_double (SP_OBJECT_REPR (next), "offset", 0);
716 }
717 }
718 break;
719 case POINT_LG_END:
720 case POINT_RG_R1:
721 case POINT_RG_R2:
722 stop = sp_last_stop(gradient);
723 {
724 SPStop *prev = sp_prev_stop (stop, gradient);
725 if (prev) {
726 prev->offset = 1;
727 sp_repr_set_css_double (SP_OBJECT_REPR (prev), "offset", 1);
728 }
729 }
730 break;
731 case POINT_LG_MID:
732 case POINT_RG_MID1:
733 case POINT_RG_MID2:
734 stop = sp_get_stop_i(gradient, draggable->point_i);
735 break;
736 }
738 SP_OBJECT_REPR(gradient)->removeChild(SP_OBJECT_REPR(stop));
739 sp_document_done (SP_OBJECT_DOCUMENT (gradient), SP_VERB_CONTEXT_GRADIENT,
740 _("Delete gradient stop"));
741 }
742 } else {
743 // select the dragger
744 dragger->point_original = dragger->point;
746 if ( state & GDK_SHIFT_MASK ) {
747 dragger->parent->setSelected (dragger, true, false);
748 } else {
749 dragger->parent->setSelected (dragger);
750 }
751 }
752 }
754 /**
755 Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
756 */
757 static void
758 gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data)
759 {
760 GrDragger *dragger = (GrDragger *) data;
762 dragger->point_original = dragger->point;
764 if (dragger->draggables == NULL)
765 return;
767 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
768 sp_item_gradient_edit_stop (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
769 }
771 /**
772 Act upon all draggables of the dragger, setting them to the dragger's point
773 */
774 void
775 GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus)
776 {
777 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
778 GrDraggable *draggable = (GrDraggable *) i->data;
780 // set local_change flag so that selection_changed callback does not regenerate draggers
781 this->parent->local_change = true;
783 // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
784 // to the center, unless it's the first update upon merge when we must snap it to the point
785 if (merging_focus ||
786 !(draggable->point_type == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->point_i, draggable->fill_or_stroke)))
787 sp_item_gradient_set_coords (draggable->item, draggable->point_type, draggable->point_i, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
788 }
789 }
791 /**
792 Checks if the dragger has a draggable with this point_type
793 */
794 bool
795 GrDragger::isA (guint point_type)
796 {
797 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
798 GrDraggable *draggable = (GrDraggable *) i->data;
799 if (draggable->point_type == point_type) {
800 return true;
801 }
802 }
803 return false;
804 }
806 /**
807 Checks if the dragger has a draggable with this item, point_type, fill_or_stroke
808 */
809 bool
810 GrDragger::isA (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
811 {
812 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
813 GrDraggable *draggable = (GrDraggable *) i->data;
814 if ( (draggable->point_type == point_type) && (draggable->point_i == point_i) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
815 return true;
816 }
817 }
818 return false;
819 }
821 bool
822 GrDraggable::mayMerge (GrDraggable *da2)
823 {
824 if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
825 // we must not merge the points of the same gradient!
826 if (!((this->point_type == POINT_RG_FOCUS && da2->point_type == POINT_RG_CENTER) ||
827 (this->point_type == POINT_RG_CENTER && da2->point_type == POINT_RG_FOCUS))) {
828 // except that we can snap center and focus together
829 return false;
830 }
831 }
832 // disable merging of midpoints.
833 if ( (this->point_type == POINT_LG_MID) || (da2->point_type == POINT_LG_MID)
834 || (this->point_type == POINT_RG_MID1) || (da2->point_type == POINT_RG_MID1)
835 || (this->point_type == POINT_RG_MID2) || (da2->point_type == POINT_RG_MID2) )
836 return false;
838 return true;
839 }
841 bool
842 GrDragger::mayMerge (GrDragger *other)
843 {
844 if (this == other)
845 return false;
847 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
848 GrDraggable *da1 = (GrDraggable *) i->data;
849 for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other
850 GrDraggable *da2 = (GrDraggable *) j->data;
851 if (!da1->mayMerge(da2))
852 return false;
853 }
854 }
855 return true;
856 }
858 bool
859 GrDragger::mayMerge (GrDraggable *da2)
860 {
861 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
862 GrDraggable *da1 = (GrDraggable *) i->data;
863 if (!da1->mayMerge(da2))
864 return false;
865 }
866 return true;
867 }
869 /**
870 Updates the statusbar tip of the dragger knot, based on its draggables
871 */
872 void
873 GrDragger::updateTip ()
874 {
875 if (this->knot && this->knot->tip) {
876 g_free (this->knot->tip);
877 this->knot->tip = NULL;
878 }
880 if (g_slist_length (this->draggables) == 1) {
881 GrDraggable *draggable = (GrDraggable *) this->draggables->data;
882 char *item_desc = sp_item_description(draggable->item);
883 switch (draggable->point_type) {
884 case POINT_LG_MID:
885 case POINT_RG_MID1:
886 case POINT_RG_MID2:
887 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"),
888 _(gr_knot_descr[draggable->point_type]),
889 draggable->point_i,
890 item_desc,
891 draggable->fill_or_stroke == false ? _(" (stroke)") : "");
892 break;
894 default:
895 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"),
896 _(gr_knot_descr[draggable->point_type]),
897 item_desc,
898 draggable->fill_or_stroke == false ? _(" (stroke)") : "");
899 break;
900 }
901 g_free(item_desc);
902 } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
903 this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
904 } else {
905 int length = g_slist_length (this->draggables);
906 this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
907 "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
908 length),
909 length);
910 }
911 }
913 /**
914 Adds a draggable to the dragger
915 */
916 void
917 GrDragger::updateKnotShape ()
918 {
919 if (!draggables)
920 return;
921 GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
922 g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_type], NULL);
923 }
925 /**
926 Adds a draggable to the dragger
927 */
928 void
929 GrDragger::addDraggable (GrDraggable *draggable)
930 {
931 this->draggables = g_slist_prepend (this->draggables, draggable);
933 this->updateTip();
934 }
937 /**
938 Moves this dragger to the point of the given draggable, acting upon all other draggables
939 */
940 void
941 GrDragger::moveThisToDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, bool write_repr)
942 {
943 this->point = sp_item_gradient_get_coords (item, point_type, point_i, fill_or_stroke);
944 this->point_original = this->point;
946 sp_knot_moveto (this->knot, &(this->point));
948 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
949 GrDraggable *da = (GrDraggable *) i->data;
950 if ( (da->item == item) && (da->point_type == point_type) && (da->point_i == point_i) && (da->fill_or_stroke == fill_or_stroke) ) {
951 continue;
952 }
953 sp_item_gradient_set_coords (da->item, da->point_type, da->point_i, this->point, da->fill_or_stroke, write_repr, false);
954 }
955 // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
956 }
959 /**
960 Moves all midstop draggables that depend on this one
961 */
962 void
963 GrDragger::updateMidstopDependencies (GrDraggable *draggable, bool write_repr) {
964 SPObject *server;
965 if (draggable->fill_or_stroke)
966 server = SP_OBJECT_STYLE_FILL_SERVER (draggable->item);
967 else
968 server = SP_OBJECT_STYLE_STROKE_SERVER (draggable->item);
969 guint num = SP_GRADIENT(server)->vector.stops.size();
970 if (num <= 2) return;
972 if ( SP_IS_LINEARGRADIENT(server) ) {
973 for ( guint i = 1; i < num - 1; i++ ) {
974 this->moveOtherToDraggable (draggable->item, POINT_LG_MID, i, draggable->fill_or_stroke, write_repr);
975 }
976 } else if ( SP_IS_RADIALGRADIENT(server) ) {
977 for ( guint i = 1; i < num - 1; i++ ) {
978 this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, i, draggable->fill_or_stroke, write_repr);
979 this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, i, draggable->fill_or_stroke, write_repr);
980 }
981 }
982 }
985 /**
986 Moves all draggables that depend on this one
987 */
988 void
989 GrDragger::updateDependencies (bool write_repr)
990 {
991 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
992 GrDraggable *draggable = (GrDraggable *) i->data;
993 switch (draggable->point_type) {
994 case POINT_LG_BEGIN:
995 {
996 // the end point is dependent only when dragging with ctrl+shift
997 this->moveOtherToDraggable (draggable->item, POINT_LG_END, 0, draggable->fill_or_stroke, write_repr);
999 this->updateMidstopDependencies (draggable, write_repr);
1000 }
1001 break;
1002 case POINT_LG_END:
1003 {
1004 // the begin point is dependent only when dragging with ctrl+shift
1005 this->moveOtherToDraggable (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke, write_repr);
1007 this->updateMidstopDependencies (draggable, write_repr);
1008 }
1009 break;
1010 case POINT_LG_MID:
1011 // no other nodes depend on mid points.
1012 break;
1013 case POINT_RG_R2:
1014 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, 0, draggable->fill_or_stroke, write_repr);
1015 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
1016 this->updateMidstopDependencies (draggable, write_repr);
1017 break;
1018 case POINT_RG_R1:
1019 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, 0, draggable->fill_or_stroke, write_repr);
1020 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
1021 this->updateMidstopDependencies (draggable, write_repr);
1022 break;
1023 case POINT_RG_CENTER:
1024 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, 0, draggable->fill_or_stroke, write_repr);
1025 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, 0, draggable->fill_or_stroke, write_repr);
1026 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
1027 this->updateMidstopDependencies (draggable, write_repr);
1028 break;
1029 case POINT_RG_FOCUS:
1030 // nothing can depend on that
1031 break;
1032 case POINT_RG_MID1:
1033 this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, draggable->point_i, draggable->fill_or_stroke, write_repr);
1034 break;
1035 case POINT_RG_MID2:
1036 this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, draggable->point_i, draggable->fill_or_stroke, write_repr);
1037 break;
1038 default:
1039 break;
1040 }
1041 }
1042 }
1046 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
1047 {
1048 this->draggables = NULL;
1050 this->parent = parent;
1052 this->point = p;
1053 this->point_original = p;
1055 // create the knot
1056 this->knot = sp_knot_new (parent->desktop, NULL);
1057 this->knot->setMode(SP_KNOT_MODE_XOR);
1058 this->knot->setFill(GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL);
1059 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
1060 sp_knot_update_ctrl(this->knot);
1062 // move knot to the given point
1063 sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
1064 sp_knot_show (this->knot);
1066 // connect knot's signals
1067 if ( (draggable) // it can be NULL if a node in unsnapped (eg. focus point unsnapped from center)
1068 // luckily, midstops never snap to other nodes so are never unsnapped...
1069 && ( (draggable->point_type == POINT_LG_MID)
1070 || (draggable->point_type == POINT_RG_MID1)
1071 || (draggable->point_type == POINT_RG_MID2) ) )
1072 {
1073 this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_midpoint_handler), this);
1074 } else {
1075 this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
1076 }
1077 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
1078 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
1079 g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (gr_knot_grabbed_handler), this);
1080 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
1082 // add the initial draggable
1083 if (draggable)
1084 this->addDraggable (draggable);
1085 updateKnotShape();
1086 }
1088 GrDragger::~GrDragger ()
1089 {
1090 // unselect if it was selected
1091 this->parent->setDeselected(this);
1093 // disconnect signals
1094 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_moved_handler), this);
1095 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_clicked_handler), this);
1096 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_doubleclicked_handler), this);
1097 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_grabbed_handler), this);
1098 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_ungrabbed_handler), this);
1100 /* unref should call destroy */
1101 g_object_unref (G_OBJECT (this->knot));
1103 // delete all draggables
1104 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
1105 delete ((GrDraggable *) i->data);
1106 }
1107 g_slist_free (this->draggables);
1108 this->draggables = NULL;
1109 }
1111 /**
1112 Select the dragger which has the given draggable.
1113 */
1114 GrDragger *
1115 GrDrag::getDraggerFor (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
1116 {
1117 for (GList const* i = this->draggers; i != NULL; i = i->next) {
1118 GrDragger *dragger = (GrDragger *) i->data;
1119 for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
1120 GrDraggable *da2 = (GrDraggable *) j->data;
1121 if ( (da2->item == item) && (da2->point_type == point_type) && (da2->point_i == point_i) && (da2->fill_or_stroke == fill_or_stroke)) {
1122 return (dragger);
1123 }
1124 }
1125 }
1126 return NULL;
1127 }
1130 void
1131 GrDragger::moveOtherToDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, bool write_repr)
1132 {
1133 GrDragger *d = this->parent->getDraggerFor (item, point_type, point_i, fill_or_stroke);
1134 if (d && d != this) {
1135 d->moveThisToDraggable (item, point_type, point_i, fill_or_stroke, write_repr);
1136 }
1137 }
1140 /**
1141 Draw this dragger as selected
1142 */
1143 void
1144 GrDragger::select()
1145 {
1146 this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
1147 g_object_set (G_OBJECT (this->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
1148 }
1150 /**
1151 Draw this dragger as normal (deselected)
1152 */
1153 void
1154 GrDragger::deselect()
1155 {
1156 this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
1157 g_object_set (G_OBJECT (this->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
1158 }
1162 /**
1163 \brief Deselect all stops/draggers
1164 */
1165 void
1166 GrDrag::deselect_all()
1167 {
1168 while (selected) {
1169 ( (GrDragger*) selected->data)->deselect();
1170 selected = g_list_remove(selected, selected->data);
1171 }
1172 }
1174 /**
1175 \brief Select a dragger
1176 \param dragger The dragger to select
1177 \param add_to_selection If true, add to selection, otherwise deselect others
1178 \param override If true, always select this node, otherwise toggle selected status
1179 */
1180 void
1181 GrDrag::setSelected (GrDragger *dragger, bool add_to_selection, bool override)
1182 {
1183 GrDragger *seldragger = NULL;
1185 if (add_to_selection) {
1186 if (!dragger) return;
1187 if (override) {
1188 if (!g_list_find(selected, dragger)) {
1189 selected = g_list_prepend(selected, dragger);
1190 }
1191 dragger->select();
1192 seldragger = dragger;
1193 } else { // toggle
1194 if (g_list_find(selected, dragger)) {
1195 selected = g_list_remove(selected, dragger);
1196 dragger->deselect();
1197 if (selected) {
1198 seldragger = (GrDragger*) selected->data; // select the dragger that is first in the list
1199 }
1200 } else {
1201 selected = g_list_prepend(selected, dragger);
1202 dragger->select();
1203 seldragger = dragger;
1204 }
1205 }
1206 } else {
1207 deselect_all();
1208 if (dragger) {
1209 selected = g_list_prepend(selected, dragger);
1210 dragger->select();
1211 seldragger = dragger;
1212 }
1213 }
1214 if (seldragger) {
1215 this->desktop->emitToolSubselectionChanged((gpointer) seldragger);
1216 }
1217 }
1219 /**
1220 \brief Deselect a dragger
1221 \param dragger The dragger to deselect
1222 */
1223 void
1224 GrDrag::setDeselected (GrDragger *dragger)
1225 {
1226 if (g_list_find(selected, dragger)) {
1227 selected = g_list_remove(selected, dragger);
1228 dragger->deselect();
1229 }
1230 this->desktop->emitToolSubselectionChanged((gpointer) (selected ? selected->data : NULL ));
1231 }
1235 /**
1236 Create a line from p1 to p2 and add it to the lines list
1237 */
1238 void
1239 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
1240 {
1241 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(this->desktop),
1242 SP_TYPE_CTRLLINE, NULL);
1243 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
1244 if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
1245 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
1246 sp_canvas_item_show (line);
1247 this->lines = g_slist_append (this->lines, line);
1248 }
1250 /**
1251 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
1252 new dragger and add it to draggers list
1253 */
1254 void
1255 GrDrag::addDragger (GrDraggable *draggable)
1256 {
1257 NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
1259 for (GList *i = this->draggers; i != NULL; i = i->next) {
1260 GrDragger *dragger = (GrDragger *) i->data;
1261 if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
1262 // distance is small, merge this draggable into dragger, no need to create new dragger
1263 dragger->addDraggable (draggable);
1264 dragger->updateKnotShape();
1265 return;
1266 }
1267 }
1269 GrDragger *new_dragger = new GrDragger(this, p, draggable);
1270 // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
1271 this->draggers = g_list_append (this->draggers, new_dragger);
1272 }
1274 /**
1275 Add draggers for the radial gradient rg on item
1276 */
1277 void
1278 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
1279 {
1280 addDragger (new GrDraggable (item, POINT_RG_CENTER, 0, fill_or_stroke));
1281 guint num = rg->vector.stops.size();
1282 if (num > 2) {
1283 for ( guint i = 1; i < num - 1; i++ ) {
1284 addDragger (new GrDraggable (item, POINT_RG_MID1, i, fill_or_stroke));
1285 }
1286 }
1287 addDragger (new GrDraggable (item, POINT_RG_R1, 0, fill_or_stroke));
1288 if (num > 2) {
1289 for ( guint i = 1; i < num - 1; i++ ) {
1290 addDragger (new GrDraggable (item, POINT_RG_MID2, i, fill_or_stroke));
1291 }
1292 }
1293 addDragger (new GrDraggable (item, POINT_RG_R2, 0, fill_or_stroke));
1294 addDragger (new GrDraggable (item, POINT_RG_FOCUS, 0, fill_or_stroke));
1295 }
1297 /**
1298 Add draggers for the linear gradient lg on item
1299 */
1300 void
1301 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
1302 {
1303 addDragger (new GrDraggable (item, POINT_LG_BEGIN, 0, fill_or_stroke));
1304 guint num = lg->vector.stops.size();
1305 if (num > 2) {
1306 for ( guint i = 1; i < num - 1; i++ ) {
1307 addDragger (new GrDraggable (item, POINT_LG_MID, i, fill_or_stroke));
1308 }
1309 }
1310 addDragger (new GrDraggable (item, POINT_LG_END, 0, fill_or_stroke));
1311 }
1313 /**
1314 Artificially grab the knot of the dragger with this draggable; used by the gradient context
1315 */
1316 void
1317 GrDrag::grabKnot (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, gint x, gint y, guint32 etime)
1318 {
1319 GrDragger *dragger = getDraggerFor (item, point_type, point_i, fill_or_stroke);
1320 if (dragger) {
1321 sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
1322 }
1323 }
1325 /**
1326 Regenerates the draggers list from the current selection; is called when selection is changed or
1327 modified, also when a radial dragger needs to update positions of other draggers in the gradient
1328 */
1329 void
1330 GrDrag::updateDraggers ()
1331 {
1332 while (selected) {
1333 selected = g_list_remove(selected, selected->data);
1334 }
1335 // delete old draggers
1336 for (GList const* i = this->draggers; i != NULL; i = i->next) {
1337 delete ((GrDragger *) i->data);
1338 }
1339 g_list_free (this->draggers);
1340 this->draggers = NULL;
1342 g_return_if_fail (this->selection != NULL);
1344 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1346 SPItem *item = SP_ITEM(i->data);
1347 SPStyle *style = SP_OBJECT_STYLE (item);
1349 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
1350 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1351 if (SP_IS_LINEARGRADIENT (server)) {
1352 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
1353 } else if (SP_IS_RADIALGRADIENT (server)) {
1354 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
1355 }
1356 }
1358 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1359 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1360 if (SP_IS_LINEARGRADIENT (server)) {
1361 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
1362 } else if (SP_IS_RADIALGRADIENT (server)) {
1363 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
1364 }
1365 }
1366 }
1367 }
1369 /**
1370 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
1371 lines are always in sync with the actual gradient
1372 */
1373 void
1374 GrDrag::updateLines ()
1375 {
1376 // delete old lines
1377 for (GSList const *i = this->lines; i != NULL; i = i->next) {
1378 gtk_object_destroy( GTK_OBJECT (i->data));
1379 }
1380 g_slist_free (this->lines);
1381 this->lines = NULL;
1383 g_return_if_fail (this->selection != NULL);
1385 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1387 SPItem *item = SP_ITEM(i->data);
1389 SPStyle *style = SP_OBJECT_STYLE (item);
1391 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
1392 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1393 if (SP_IS_LINEARGRADIENT (server)) {
1394 this->addLine (sp_item_gradient_get_coords (item, POINT_LG_BEGIN, 0, true), sp_item_gradient_get_coords (item, POINT_LG_END, 0, true), GR_LINE_COLOR_FILL);
1395 } else if (SP_IS_RADIALGRADIENT (server)) {
1396 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, 0, true);
1397 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, true), GR_LINE_COLOR_FILL);
1398 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, true), GR_LINE_COLOR_FILL);
1399 }
1400 }
1402 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1403 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1404 if (SP_IS_LINEARGRADIENT (server)) {
1405 this->addLine (sp_item_gradient_get_coords (item, POINT_LG_BEGIN, 0, false), sp_item_gradient_get_coords (item, POINT_LG_END, 0, false), GR_LINE_COLOR_STROKE);
1406 } else if (SP_IS_RADIALGRADIENT (server)) {
1407 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, 0, false);
1408 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, false), GR_LINE_COLOR_STROKE);
1409 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, false), GR_LINE_COLOR_STROKE);
1410 }
1411 }
1412 }
1413 }
1415 /**
1416 Regenerates the levels list from the current selection
1417 */
1418 void
1419 GrDrag::updateLevels ()
1420 {
1421 hor_levels.clear();
1422 vert_levels.clear();
1424 g_return_if_fail (this->selection != NULL);
1426 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1427 SPItem *item = SP_ITEM(i->data);
1428 NR::Rect rect = sp_item_bbox_desktop (item);
1429 // Remember the edges of the bbox and the center axis
1430 hor_levels.push_back(rect.min()[NR::Y]);
1431 hor_levels.push_back(rect.max()[NR::Y]);
1432 hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1433 vert_levels.push_back(rect.min()[NR::X]);
1434 vert_levels.push_back(rect.max()[NR::X]);
1435 vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1436 }
1437 }
1439 void
1440 GrDrag::selected_reverse_vector ()
1441 {
1442 if (selected == NULL)
1443 return;
1445 for (GSList const* i = ( (GrDragger*) selected->data )->draggables; i != NULL; i = i->next) {
1446 GrDraggable *draggable = (GrDraggable *) i->data;
1448 sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1449 }
1450 }
1452 void
1453 GrDrag::selected_move (double x, double y)
1454 {
1455 /*
1456 if (selected == NULL)
1457 return;
1459 if ( (g_list_length(selected) == 1) )
1460 selected->point += NR::Point (x, y);
1461 selected->point_original = selected->point;
1462 sp_knot_moveto (selected->knot, &(selected->point));
1464 selected->fireDraggables (true);
1466 selected->updateDependencies(true);
1468 // we did an undoable action
1469 sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
1470 _("Move gradient handle"));
1471 */
1472 }
1474 void
1475 GrDrag::selected_move_screen (double x, double y)
1476 {
1477 gdouble zoom = desktop->current_zoom();
1478 gdouble zx = x / zoom;
1479 gdouble zy = y / zoom;
1481 selected_move (zx, zy);
1482 }
1484 /**
1485 Select the knot next to the last selected one and deselect all other selected.
1486 */
1487 void
1488 GrDrag::select_next ()
1489 {
1490 if (selected == NULL || g_list_find(draggers, selected->data)->next == NULL) {
1491 if (draggers)
1492 setSelected ((GrDragger *) draggers->data);
1493 } else {
1494 setSelected ((GrDragger *) g_list_find(draggers, selected->data)->next->data);
1495 }
1496 }
1498 /**
1499 Select the knot previous from the last selected one and deselect all other selected.
1500 */
1501 void
1502 GrDrag::select_prev ()
1503 {
1504 if (selected == NULL || g_list_find(draggers, selected->data)->prev == NULL) {
1505 if (draggers)
1506 setSelected ((GrDragger *) g_list_last (draggers)->data);
1507 } else {
1508 setSelected ((GrDragger *) g_list_find(draggers, selected->data)->prev->data);
1509 }
1510 }
1513 /*
1514 Local Variables:
1515 mode:c++
1516 c-file-style:"stroustrup"
1517 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1518 indent-tabs-mode:nil
1519 fill-column:99
1520 End:
1521 */
1522 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :