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"
38 #define GR_KNOT_COLOR_NORMAL 0xffffff00
39 #define GR_KNOT_COLOR_SELECTED 0x0000ff00
41 #define GR_LINE_COLOR_FILL 0x0000ff7f
42 #define GR_LINE_COLOR_STROKE 0x9999007f
44 // screen pixels between knots when they snap:
45 #define SNAP_DIST 5
47 // absolute distance between gradient points for them to become a single dragger when the drag is created:
48 #define MERGE_DIST 0.1
50 // knot shapes corresponding to GrPointType enum
51 SPKnotShapeType gr_knot_shapes [] = {
52 SP_KNOT_SHAPE_SQUARE, //POINT_LG_BEGIN
53 SP_KNOT_SHAPE_CIRCLE, //POINT_LG_END
54 SP_KNOT_SHAPE_DIAMOND, //POINT_LG_MID
55 SP_KNOT_SHAPE_DIAMOND,
56 SP_KNOT_SHAPE_CIRCLE,
57 SP_KNOT_SHAPE_CIRCLE,
58 SP_KNOT_SHAPE_CROSS // POINT_RG_FOCUS
59 };
61 const gchar *gr_knot_descr [] = {
62 N_("Linear gradient <b>start</b>"), //POINT_LG_BEGIN
63 N_("Linear gradient <b>end</b>"),
64 N_("Linear gradient <b>mid</b>"),
65 N_("Radial gradient <b>center</b>"),
66 N_("Radial gradient <b>radius</b>"),
67 N_("Radial gradient <b>radius</b>"),
68 N_("Radial gradient <b>focus</b>") // POINT_RG_FOCUS
69 };
71 static void
72 gr_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
73 {
74 GrDrag *drag = (GrDrag *) data;
75 drag->updateDraggers ();
76 drag->updateLines ();
77 drag->updateLevels ();
78 }
80 static void
81 gr_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
82 {
83 GrDrag *drag = (GrDrag *) data;
84 if (drag->local_change) {
85 drag->local_change = false;
86 } else {
87 drag->updateDraggers ();
88 }
89 drag->updateLines ();
90 drag->updateLevels ();
91 }
93 /**
94 When a _query_style_signal is received, check that \a property requests fill/stroke (otherwise
95 skip), and fill the \a style with the averaged color of all draggables of the selected dragger, if
96 any.
97 */
98 int
99 gr_drag_style_query (SPStyle *style, int property, gpointer data)
100 {
101 GrDrag *drag = (GrDrag *) data;
103 if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE) {
104 return QUERY_STYLE_NOTHING;
105 }
107 if (!drag->selected) {
108 return QUERY_STYLE_NOTHING;
109 } else {
110 int ret = QUERY_STYLE_NOTHING;
112 float cf[4];
113 cf[0] = cf[1] = cf[2] = cf[3] = 0;
115 int count = 0;
117 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
118 GrDraggable *draggable = (GrDraggable *) i->data;
120 if (ret == QUERY_STYLE_NOTHING) {
121 ret = QUERY_STYLE_SINGLE;
122 } else if (ret == QUERY_STYLE_SINGLE) {
123 ret = QUERY_STYLE_MULTIPLE_AVERAGED;
124 }
126 guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
127 cf[0] += SP_RGBA32_R_F (c);
128 cf[1] += SP_RGBA32_G_F (c);
129 cf[2] += SP_RGBA32_B_F (c);
130 cf[3] += SP_RGBA32_A_F (c);
132 count ++;
133 }
135 if (count) {
136 cf[0] /= count;
137 cf[1] /= count;
138 cf[2] /= count;
139 cf[3] /= count;
141 // set both fill and stroke with our stop-color and stop-opacity
142 sp_color_set_rgb_float((SPColor *) &style->fill.value.color, cf[0], cf[1], cf[2]);
143 style->fill.set = TRUE;
144 style->fill.type = SP_PAINT_TYPE_COLOR;
145 sp_color_set_rgb_float((SPColor *) &style->stroke.value.color, cf[0], cf[1], cf[2]);
146 style->stroke.set = TRUE;
147 style->stroke.type = SP_PAINT_TYPE_COLOR;
149 style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
150 style->fill_opacity.set = TRUE;
151 style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
152 style->stroke_opacity.set = TRUE;
153 }
155 return ret;
156 }
157 }
159 bool
160 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
161 {
162 GrDrag *drag = (GrDrag *) data;
164 if (!drag->selected)
165 return false;
167 SPCSSAttr *stop = sp_repr_css_attr_new ();
169 // See if the css contains interesting properties, and if so, translate them into the format
170 // acceptable for gradient stops
172 // any of color properties, in order of increasing priority:
173 if (css->attribute("flood-color"))
174 sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color"));
176 if (css->attribute("lighting-color"))
177 sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color"));
179 if (css->attribute("color"))
180 sp_repr_css_set_property (stop, "stop-color", css->attribute("color"));
182 if (css->attribute("stroke") && strcmp(css->attribute("stroke"), "none"))
183 sp_repr_css_set_property (stop, "stop-color", css->attribute("stroke"));
185 if (css->attribute("fill") && strcmp(css->attribute("fill"), "none"))
186 sp_repr_css_set_property (stop, "stop-color", css->attribute("fill"));
188 if (css->attribute("stop-color"))
189 sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color"));
191 // any of opacity properties, in order of increasing priority:
192 if (css->attribute("flood-opacity"))
193 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("flood-color"));
195 if (css->attribute("opacity")) // TODO: multiply
196 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("color"));
198 if (css->attribute("stroke-opacity")) // TODO: multiply
199 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stroke-opacity"));
201 if (css->attribute("fill-opacity")) // TODO: multiply
202 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("fill-opacity"));
204 if ((css->attribute("fill") && !strcmp(css->attribute("fill"), "none")) ||
205 (css->attribute("stroke") && !strcmp(css->attribute("stroke"), "none")))
206 sp_repr_css_set_property (stop, "stop-opacity", "0"); // if set to none, don't change color, set opacity to 0
208 if (css->attribute("stop-opacity"))
209 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stop-opacity"));
211 if (!stop->attributeList()) { // nothing for us here, pass it on
212 sp_repr_css_attr_unref(stop);
213 return false;
214 }
216 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
217 GrDraggable *draggable = (GrDraggable *) i->data;
219 drag->local_change = true;
220 sp_item_gradient_stop_set_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke, stop);
221 }
223 //sp_repr_css_print(stop);
224 sp_repr_css_attr_unref(stop);
225 return true;
226 }
228 GrDrag::GrDrag(SPDesktop *desktop) {
230 this->desktop = desktop;
232 this->selection = sp_desktop_selection(desktop);
234 this->draggers = NULL;
235 this->lines = NULL;
236 this->selected = NULL;
238 this->hor_levels.clear();
239 this->vert_levels.clear();
241 this->local_change = false;
243 this->sel_changed_connection = this->selection->connectChanged(
244 sigc::bind (
245 sigc::ptr_fun(&gr_drag_sel_changed),
246 (gpointer)this )
248 );
249 this->sel_modified_connection = this->selection->connectModified(
250 sigc::bind(
251 sigc::ptr_fun(&gr_drag_sel_modified),
252 (gpointer)this )
253 );
255 this->style_set_connection = this->desktop->connectSetStyle(
256 sigc::bind(
257 sigc::ptr_fun(&gr_drag_style_set),
258 (gpointer)this )
259 );
261 this->style_query_connection = this->desktop->connectQueryStyle(
262 sigc::bind(
263 sigc::ptr_fun(&gr_drag_style_query),
264 (gpointer)this )
265 );
267 this->updateDraggers ();
268 this->updateLines ();
269 this->updateLevels ();
271 if (desktop->gr_item) {
272 this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_type, desktop->gr_point_i, desktop->gr_fill_or_stroke));
273 }
274 }
276 GrDrag::~GrDrag()
277 {
278 this->sel_changed_connection.disconnect();
279 this->sel_modified_connection.disconnect();
280 this->style_set_connection.disconnect();
281 this->style_query_connection.disconnect();
283 if (this->selected) {
284 GrDraggable *draggable = (GrDraggable *) this->selected->draggables->data;
285 desktop->gr_item = draggable->item;
286 desktop->gr_point_type = draggable->point_type;
287 desktop->gr_point_i = draggable->point_i;
288 desktop->gr_fill_or_stroke = draggable->fill_or_stroke;
289 } else {
290 desktop->gr_item = NULL;
291 desktop->gr_point_type = 0;
292 desktop->gr_point_i = 0;
293 desktop->gr_fill_or_stroke = true;
294 }
296 for (GList *l = this->draggers; l != NULL; l = l->next) {
297 delete ((GrDragger *) l->data);
298 }
299 g_list_free (this->draggers);
300 this->draggers = NULL;
301 this->selected = NULL;
303 for (GSList *l = this->lines; l != NULL; l = l->next) {
304 gtk_object_destroy( GTK_OBJECT (l->data));
305 }
306 g_slist_free (this->lines);
307 this->lines = NULL;
308 }
310 GrDraggable::GrDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
311 {
312 this->item = item;
313 this->point_type = point_type;
314 this->point_i = point_i;
315 this->fill_or_stroke = fill_or_stroke;
317 g_object_ref (G_OBJECT (this->item));
318 }
320 GrDraggable::~GrDraggable ()
321 {
322 g_object_unref (G_OBJECT (this->item));
323 }
325 NR::Point *
326 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
327 {
328 double r = NR::L2 (p - o);
329 if (r < 1e-3)
330 return NULL;
331 double angle = NR::atan2 (p - o);
332 // snap angle to snaps increments, starting from initial:
333 double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap;
334 // calculate the new position and subtract p to get the vector:
335 return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p);
336 }
338 static void
339 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
340 {
341 GrDragger *dragger = (GrDragger *) data;
343 NR::Point p = *ppointer;
345 // FIXME: take from prefs
346 double snap_dist = SNAP_DIST / dragger->parent->desktop->current_zoom();
348 if (state & GDK_SHIFT_MASK) {
349 // with Shift; unsnap if we carry more than one draggable
350 if (dragger->draggables && dragger->draggables->next) {
351 // create a new dragger
352 GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, NULL);
353 dragger->parent->draggers = g_list_prepend (dragger->parent->draggers, dr_new);
354 // relink to it all but the first draggable in the list
355 for (GSList const* i = dragger->draggables->next; i != NULL; i = i->next) {
356 GrDraggable *draggable = (GrDraggable *) i->data;
357 dr_new->addDraggable (draggable);
358 }
359 dr_new->updateKnotShape();
360 g_slist_free (dragger->draggables->next);
361 dragger->draggables->next = NULL;
362 dragger->updateKnotShape();
363 dragger->updateTip();
364 }
365 } else if (!(state & GDK_CONTROL_MASK)) {
366 // without Shift or Ctrl; see if we need to snap to another dragger
367 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
368 GrDragger *d_new = (GrDragger *) di->data;
369 if (dragger->mayMerge(d_new) && NR::L2 (d_new->point - p) < snap_dist) {
371 // Merge draggers:
372 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
373 GrDraggable *draggable = (GrDraggable *) i->data;
374 // copy draggable to d_new:
375 GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
376 d_new->addDraggable (da_new);
377 }
379 // unlink and delete this dragger
380 dragger->parent->draggers = g_list_remove (dragger->parent->draggers, dragger);
381 delete dragger;
383 // update the new merged dragger
384 d_new->fireDraggables(true, false, true);
385 d_new->parent->updateLines();
386 d_new->parent->setSelected (d_new);
387 d_new->updateKnotShape ();
388 d_new->updateTip ();
389 d_new->updateDependencies(true);
390 sp_document_done (sp_desktop_document (d_new->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
391 _("Merge gradient handles"));
392 return;
393 }
394 }
395 }
397 if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) {
398 // See if we need to snap to any of the levels
399 for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) {
400 if (fabs(p[NR::Y] - dragger->parent->hor_levels[i]) < snap_dist) {
401 p[NR::Y] = dragger->parent->hor_levels[i];
402 sp_knot_moveto (knot, &p);
403 }
404 }
405 for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) {
406 if (fabs(p[NR::X] - dragger->parent->vert_levels[i]) < snap_dist) {
407 p[NR::X] = dragger->parent->vert_levels[i];
408 sp_knot_moveto (knot, &p);
409 }
410 }
411 }
413 if (state & GDK_CONTROL_MASK) {
414 unsigned snaps = abs(prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12));
415 /* 0 means no snapping. */
417 // This list will store snap vectors from all draggables of dragger
418 GSList *snap_vectors = NULL;
420 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) {
421 GrDraggable *draggable = (GrDraggable *) i->data;
423 NR::Point *dr_snap = NULL;
425 if (draggable->point_type == POINT_LG_BEGIN || draggable->point_type == POINT_LG_END) {
426 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
427 GrDragger *d_new = (GrDragger *) di->data;
428 if (d_new == dragger)
429 continue;
430 if (d_new->isA (draggable->item,
431 draggable->point_type == POINT_LG_BEGIN? POINT_LG_END : POINT_LG_BEGIN,
432 draggable->point_i,
433 draggable->fill_or_stroke)) {
434 // found the other end of the linear gradient;
435 if (state & GDK_SHIFT_MASK) {
436 // moving linear around center
437 NR::Point center = NR::Point (0.5*(d_new->point + dragger->point));
438 dr_snap = ¢er;
439 } else {
440 // moving linear around the other end
441 dr_snap = &d_new->point;
442 }
443 }
444 }
445 } else if (draggable->point_type == POINT_RG_R1 || draggable->point_type == POINT_RG_R2 || draggable->point_type == POINT_RG_FOCUS) {
446 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
447 GrDragger *d_new = (GrDragger *) di->data;
448 if (d_new == dragger)
449 continue;
450 if (d_new->isA (draggable->item,
451 POINT_RG_CENTER,
452 draggable->point_i,
453 draggable->fill_or_stroke)) {
454 // found the center of the radial gradient;
455 dr_snap = &(d_new->point);
456 }
457 }
458 } else if (draggable->point_type == POINT_RG_CENTER) {
459 // radial center snaps to hor/vert relative to its original position
460 dr_snap = &(dragger->point_original);
461 }
463 NR::Point *snap_vector = NULL;
464 if (dr_snap) {
465 if (state & GDK_MOD1_MASK) {
466 // with Alt, snap to the original angle and its perpendiculars
467 snap_vector = get_snap_vector (p, *dr_snap, M_PI/2, NR::atan2 (dragger->point_original - *dr_snap));
468 } else {
469 // with Ctrl, snap to M_PI/snaps
470 snap_vector = get_snap_vector (p, *dr_snap, M_PI/snaps, 0);
471 }
472 }
473 if (snap_vector) {
474 snap_vectors = g_slist_prepend (snap_vectors, snap_vector);
475 }
476 }
478 // Move by the smallest of snap vectors:
479 NR::Point move(9999, 9999);
480 for (GSList const *i = snap_vectors; i != NULL; i = i->next) {
481 NR::Point *snap_vector = (NR::Point *) i->data;
482 if (NR::L2(*snap_vector) < NR::L2(move))
483 move = *snap_vector;
484 }
485 if (move[NR::X] < 9999) {
486 p += move;
487 sp_knot_moveto (knot, &p);
488 }
489 }
491 dragger->point = p;
493 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
494 dragger->fireDraggables (false, true);
495 } else {
496 dragger->fireDraggables (false);
497 }
499 dragger->updateDependencies(false);
500 }
502 /**
503 Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
504 */
505 static void
506 gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
507 {
508 GrDragger *dragger = (GrDragger *) data;
510 dragger->point_original = dragger->point = knot->pos;
512 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
513 dragger->fireDraggables (true, true);
514 } else {
515 dragger->fireDraggables (true);
516 }
518 // make this dragger selected
519 dragger->parent->setSelected (dragger);
521 dragger->updateDependencies(true);
523 // we did an undoable action
524 sp_document_done (sp_desktop_document (dragger->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
525 _("Move gradient handle"));
526 }
528 /**
529 Called when a dragger knot is clicked; selects the dragger
530 */
531 static void
532 gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
533 {
534 GrDragger *dragger = (GrDragger *) data;
536 dragger->point_original = dragger->point;
538 dragger->parent->setSelected (dragger);
539 }
541 /**
542 Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
543 */
544 static void
545 gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data)
546 {
547 GrDragger *dragger = (GrDragger *) data;
549 dragger->point_original = dragger->point;
551 if (dragger->draggables == NULL)
552 return;
554 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
555 sp_item_gradient_edit_stop (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
556 }
558 /**
559 Act upon all draggables of the dragger, setting them to the dragger's point
560 */
561 void
562 GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus)
563 {
564 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
565 GrDraggable *draggable = (GrDraggable *) i->data;
567 // set local_change flag so that selection_changed callback does not regenerate draggers
568 this->parent->local_change = true;
570 // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
571 // to the center, unless it's the first update upon merge when we must snap it to the point
572 if (merging_focus ||
573 !(draggable->point_type == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->point_i, draggable->fill_or_stroke)))
574 sp_item_gradient_set_coords (draggable->item, draggable->point_type, draggable->point_i, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
575 }
576 }
578 /**
579 Checks if the dragger has a draggable with this point_type
580 */
581 bool
582 GrDragger::isA (guint point_type)
583 {
584 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
585 GrDraggable *draggable = (GrDraggable *) i->data;
586 if (draggable->point_type == point_type) {
587 return true;
588 }
589 }
590 return false;
591 }
593 /**
594 Checks if the dragger has a draggable with this item, point_type, fill_or_stroke
595 */
596 bool
597 GrDragger::isA (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
598 {
599 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
600 GrDraggable *draggable = (GrDraggable *) i->data;
601 if ( (draggable->point_type == point_type) && (draggable->point_i == point_i) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
602 return true;
603 }
604 }
605 return false;
606 }
608 bool
609 GrDraggable::mayMerge (GrDraggable *da2)
610 {
611 if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
612 // we must not merge the points of the same gradient!
613 if (!((this->point_type == POINT_RG_FOCUS && da2->point_type == POINT_RG_CENTER) ||
614 (this->point_type == POINT_RG_CENTER && da2->point_type == POINT_RG_FOCUS))) {
615 // except that we can snap center and focus together
616 return false;
617 }
618 }
619 return true;
620 }
622 bool
623 GrDragger::mayMerge (GrDragger *other)
624 {
625 if (this == other)
626 return false;
628 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
629 GrDraggable *da1 = (GrDraggable *) i->data;
630 for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other
631 GrDraggable *da2 = (GrDraggable *) j->data;
632 if (!da1->mayMerge(da2))
633 return false;
634 }
635 }
636 return true;
637 }
639 bool
640 GrDragger::mayMerge (GrDraggable *da2)
641 {
642 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
643 GrDraggable *da1 = (GrDraggable *) i->data;
644 if (!da1->mayMerge(da2))
645 return false;
646 }
647 return true;
648 }
650 /**
651 Updates the statusbar tip of the dragger knot, based on its draggables
652 */
653 void
654 GrDragger::updateTip ()
655 {
656 if (this->knot && this->knot->tip) {
657 g_free (this->knot->tip);
658 this->knot->tip = NULL;
659 }
661 if (g_slist_length (this->draggables) == 1) {
662 GrDraggable *draggable = (GrDraggable *) this->draggables->data;
663 char *item_desc = sp_item_description(draggable->item);
664 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"),
665 _(gr_knot_descr[draggable->point_type]),
666 item_desc,
667 draggable->fill_or_stroke == false ? _(" (stroke)") : "");
668 g_free(item_desc);
669 } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
670 this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
671 } else {
672 int length = g_slist_length (this->draggables);
673 this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
674 "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
675 length),
676 length);
677 }
678 }
680 /**
681 Adds a draggable to the dragger
682 */
683 void
684 GrDragger::updateKnotShape ()
685 {
686 if (!draggables)
687 return;
688 GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
689 g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_type], NULL);
690 }
692 /**
693 Adds a draggable to the dragger
694 */
695 void
696 GrDragger::addDraggable (GrDraggable *draggable)
697 {
698 this->draggables = g_slist_prepend (this->draggables, draggable);
700 this->updateTip();
701 }
704 /**
705 Moves this dragger to the point of the given draggable, acting upon all other draggables
706 */
707 void
708 GrDragger::moveThisToDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, bool write_repr)
709 {
710 this->point = sp_item_gradient_get_coords (item, point_type, point_i, fill_or_stroke);
711 this->point_original = this->point;
713 sp_knot_moveto (this->knot, &(this->point));
715 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
716 GrDraggable *da = (GrDraggable *) i->data;
717 if ( (da->item == item) && (da->point_type == point_type) && (da->point_i == point_i) && (da->fill_or_stroke == fill_or_stroke) ) {
718 continue;
719 }
720 sp_item_gradient_set_coords (da->item, da->point_type, da->point_i, this->point, da->fill_or_stroke, write_repr, false);
721 }
722 // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
723 }
726 /**
727 Moves all draggables that depend on this one
728 */
729 void
730 GrDragger::updateDependencies (bool write_repr)
731 {
732 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
733 GrDraggable *draggable = (GrDraggable *) i->data;
734 switch (draggable->point_type) {
735 case POINT_LG_BEGIN:
736 {
737 // the end point is dependent only when dragging with ctrl+shift
738 this->moveOtherToDraggable (draggable->item, POINT_LG_END, 0, draggable->fill_or_stroke, write_repr);
740 // update all midpoints.
741 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (draggable->item);
742 guint num = SP_GRADIENT(server)->vector.stops.size();
743 if (num > 2) {
744 for ( guint i = 1; i < num - 1; i++ ) {
745 this->moveOtherToDraggable (draggable->item, POINT_LG_MID, i, draggable->fill_or_stroke, write_repr);
746 }
747 }
748 }
749 break;
750 case POINT_LG_END:
751 {
752 // the begin point is dependent only when dragging with ctrl+shift
753 this->moveOtherToDraggable (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke, write_repr);
755 // update all midpoints.
756 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (draggable->item);
757 guint num = SP_GRADIENT(server)->vector.stops.size();
758 if (num > 2) {
759 for ( guint i = 1; i < num - 1; i++ ) {
760 this->moveOtherToDraggable (draggable->item, POINT_LG_MID, i, draggable->fill_or_stroke, write_repr);
761 }
762 }
763 }
764 break;
765 case POINT_LG_MID:
766 // no other nodes depend on mid points.
767 break;
768 case POINT_RG_R2:
769 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, 0, draggable->fill_or_stroke, write_repr);
770 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
771 break;
772 case POINT_RG_R1:
773 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, 0, draggable->fill_or_stroke, write_repr);
774 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
775 break;
776 case POINT_RG_CENTER:
777 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, 0, draggable->fill_or_stroke, write_repr);
778 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, 0, draggable->fill_or_stroke, write_repr);
779 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, 0, draggable->fill_or_stroke, write_repr);
780 break;
781 case POINT_RG_FOCUS:
782 // nothing can depend on that
783 break;
784 default:
785 break;
786 }
787 }
788 }
792 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
793 {
794 this->draggables = NULL;
796 this->parent = parent;
798 this->point = p;
799 this->point_original = p;
801 // create the knot
802 this->knot = sp_knot_new (parent->desktop, NULL);
803 this->knot->setMode(SP_KNOT_MODE_XOR);
804 this->knot->setFill(GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL);
805 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
806 sp_knot_update_ctrl(this->knot);
808 // move knot to the given point
809 sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
810 sp_knot_show (this->knot);
812 // connect knot's signals
813 this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
814 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
815 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
816 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
818 // add the initial draggable
819 if (draggable)
820 this->addDraggable (draggable);
821 updateKnotShape();
822 }
824 GrDragger::~GrDragger ()
825 {
826 // unselect if it was selected
827 if (this->parent->selected == this)
828 this->parent->setSelected (NULL);
830 /* unref should call destroy */
831 g_object_unref (G_OBJECT (this->knot));
833 // delete all draggables
834 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
835 delete ((GrDraggable *) i->data);
836 }
837 g_slist_free (this->draggables);
838 this->draggables = NULL;
839 }
841 /**
842 Select the dragger which has the given draggable.
843 */
844 GrDragger *
845 GrDrag::getDraggerFor (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
846 {
847 for (GList const* i = this->draggers; i != NULL; i = i->next) {
848 GrDragger *dragger = (GrDragger *) i->data;
849 for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
850 GrDraggable *da2 = (GrDraggable *) j->data;
851 if ( (da2->item == item) && (da2->point_type == point_type) && (da2->point_i == point_i) && (da2->fill_or_stroke == fill_or_stroke)) {
852 return (dragger);
853 }
854 }
855 }
856 return NULL;
857 }
860 void
861 GrDragger::moveOtherToDraggable (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, bool write_repr)
862 {
863 GrDragger *d = this->parent->getDraggerFor (item, point_type, point_i, fill_or_stroke);
864 if (d && d != this) {
865 d->moveThisToDraggable (item, point_type, point_i, fill_or_stroke, write_repr);
866 }
867 }
870 /**
871 Set selected dragger
872 */
873 void
874 GrDrag::setSelected (GrDragger *dragger)
875 {
876 if (this->selected) {
877 this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
878 g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
879 }
880 if (dragger) {
881 dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
882 g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
883 }
884 this->selected = dragger;
886 this->desktop->emitToolSubselectionChanged((gpointer) dragger);
887 }
889 /**
890 Create a line from p1 to p2 and add it to the lines list
891 */
892 void
893 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
894 {
895 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(this->desktop),
896 SP_TYPE_CTRLLINE, NULL);
897 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
898 if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
899 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
900 sp_canvas_item_show (line);
901 this->lines = g_slist_append (this->lines, line);
902 }
904 /**
905 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
906 new dragger and add it to draggers list
907 */
908 void
909 GrDrag::addDragger (GrDraggable *draggable)
910 {
911 NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
913 for (GList *i = this->draggers; i != NULL; i = i->next) {
914 GrDragger *dragger = (GrDragger *) i->data;
915 if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
916 // distance is small, merge this draggable into dragger, no need to create new dragger
917 dragger->addDraggable (draggable);
918 dragger->updateKnotShape();
919 return;
920 }
921 }
923 GrDragger *new_dragger = new GrDragger(this, p, draggable);
924 this->draggers = g_list_prepend (this->draggers, new_dragger);
925 }
927 /**
928 Add draggers for the radial gradient rg on item
929 */
930 void
931 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
932 {
933 addDragger (new GrDraggable (item, POINT_RG_CENTER, 0, fill_or_stroke));
934 addDragger (new GrDraggable (item, POINT_RG_FOCUS, 0, fill_or_stroke));
935 addDragger (new GrDraggable (item, POINT_RG_R1, 0, fill_or_stroke));
936 addDragger (new GrDraggable (item, POINT_RG_R2, 0, fill_or_stroke));
937 }
939 /**
940 Add draggers for the linear gradient lg on item
941 */
942 void
943 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
944 {
945 addDragger (new GrDraggable (item, POINT_LG_BEGIN, 0, fill_or_stroke));
947 // add midstops if any:
948 guint num = lg->vector.stops.size();
949 if (num > 2) {
950 for ( guint i = 1; i < num - 1; i++ ) {
951 addDragger (new GrDraggable (item, POINT_LG_MID, i, fill_or_stroke));
952 }
953 }
955 addDragger (new GrDraggable (item, POINT_LG_END, 0, fill_or_stroke));
956 }
958 /**
959 Artificially grab the knot of the dragger with this draggable; used by the gradient context
960 */
961 void
962 GrDrag::grabKnot (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, gint x, gint y, guint32 etime)
963 {
964 GrDragger *dragger = getDraggerFor (item, point_type, point_i, fill_or_stroke);
965 if (dragger) {
966 sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
967 }
968 }
970 /**
971 Regenerates the draggers list from the current selection; is called when selection is changed or
972 modified, also when a radial dragger needs to update positions of other draggers in the gradient
973 */
974 void
975 GrDrag::updateDraggers ()
976 {
977 // delete old draggers and deselect
978 for (GList const* i = this->draggers; i != NULL; i = i->next) {
979 delete ((GrDragger *) i->data);
980 }
981 g_list_free (this->draggers);
982 this->draggers = NULL;
983 this->selected = NULL;
985 g_return_if_fail (this->selection != NULL);
987 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
989 SPItem *item = SP_ITEM(i->data);
990 SPStyle *style = SP_OBJECT_STYLE (item);
992 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
993 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
994 if (SP_IS_LINEARGRADIENT (server)) {
995 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
996 } else if (SP_IS_RADIALGRADIENT (server)) {
997 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
998 }
999 }
1001 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1002 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1003 if (SP_IS_LINEARGRADIENT (server)) {
1004 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
1005 } else if (SP_IS_RADIALGRADIENT (server)) {
1006 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
1007 }
1008 }
1011 }
1012 }
1014 /**
1015 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
1016 lines are always in sync with the actual gradient
1017 */
1018 void
1019 GrDrag::updateLines ()
1020 {
1021 // delete old lines
1022 for (GSList const *i = this->lines; i != NULL; i = i->next) {
1023 gtk_object_destroy( GTK_OBJECT (i->data));
1024 }
1025 g_slist_free (this->lines);
1026 this->lines = NULL;
1028 g_return_if_fail (this->selection != NULL);
1030 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1032 SPItem *item = SP_ITEM(i->data);
1034 SPStyle *style = SP_OBJECT_STYLE (item);
1036 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
1037 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1038 if (SP_IS_LINEARGRADIENT (server)) {
1039 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);
1040 } else if (SP_IS_RADIALGRADIENT (server)) {
1041 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, 0, true);
1042 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, true), GR_LINE_COLOR_FILL);
1043 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, true), GR_LINE_COLOR_FILL);
1044 }
1045 }
1047 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1048 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1049 if (SP_IS_LINEARGRADIENT (server)) {
1050 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);
1051 } else if (SP_IS_RADIALGRADIENT (server)) {
1052 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, 0, false);
1053 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, false), GR_LINE_COLOR_STROKE);
1054 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, false), GR_LINE_COLOR_STROKE);
1055 }
1056 }
1057 }
1058 }
1060 /**
1061 Regenerates the levels list from the current selection
1062 */
1063 void
1064 GrDrag::updateLevels ()
1065 {
1066 hor_levels.clear();
1067 vert_levels.clear();
1069 g_return_if_fail (this->selection != NULL);
1071 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1072 SPItem *item = SP_ITEM(i->data);
1073 NR::Rect rect = sp_item_bbox_desktop (item);
1074 // Remember the edges of the bbox and the center axis
1075 hor_levels.push_back(rect.min()[NR::Y]);
1076 hor_levels.push_back(rect.max()[NR::Y]);
1077 hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1078 vert_levels.push_back(rect.min()[NR::X]);
1079 vert_levels.push_back(rect.max()[NR::X]);
1080 vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1081 }
1082 }
1084 void
1085 GrDrag::selected_reverse_vector ()
1086 {
1087 if (selected == NULL)
1088 return;
1090 for (GSList const* i = selected->draggables; i != NULL; i = i->next) {
1091 GrDraggable *draggable = (GrDraggable *) i->data;
1093 sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1094 }
1095 }
1097 void
1098 GrDrag::selected_move (double x, double y)
1099 {
1100 if (selected == NULL)
1101 return;
1103 selected->point += NR::Point (x, y);
1104 selected->point_original = selected->point;
1105 sp_knot_moveto (selected->knot, &(selected->point));
1107 selected->fireDraggables (true);
1109 selected->updateDependencies(true);
1111 // we did an undoable action
1112 sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
1113 _("Move gradient handle"));
1114 }
1116 void
1117 GrDrag::selected_move_screen (double x, double y)
1118 {
1119 gdouble zoom = desktop->current_zoom();
1120 gdouble zx = x / zoom;
1121 gdouble zy = y / zoom;
1123 selected_move (zx, zy);
1124 }
1126 void
1127 GrDrag::select_next ()
1128 {
1129 if (selected == NULL || g_list_find(draggers, selected)->next == NULL) {
1130 if (draggers)
1131 setSelected ((GrDragger *) draggers->data);
1132 } else {
1133 setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data);
1134 }
1135 }
1137 void
1138 GrDrag::select_prev ()
1139 {
1140 if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) {
1141 if (draggers)
1142 setSelected ((GrDragger *) g_list_last (draggers)->data);
1143 } else {
1144 setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data);
1145 }
1146 }
1149 /*
1150 Local Variables:
1151 mode:c++
1152 c-file-style:"stroustrup"
1153 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1154 indent-tabs-mode:nil
1155 fill-column:99
1156 End:
1157 */
1158 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :