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 #include <glibmm/i18n.h>
16 #include "desktop-handles.h"
17 #include "selection.h"
18 #include "desktop.h"
19 #include "desktop-style.h"
20 #include "document.h"
21 #include "display/sp-ctrlline.h"
23 #include "xml/repr.h"
25 #include "prefs-utils.h"
26 #include "sp-item.h"
27 #include "style.h"
28 #include "knot.h"
29 #include "sp-linear-gradient.h"
30 #include "sp-radial-gradient.h"
31 #include "gradient-chemistry.h"
32 #include "gradient-drag.h"
34 #define GR_KNOT_COLOR_NORMAL 0xffffff00
35 #define GR_KNOT_COLOR_SELECTED 0x0000ff00
37 #define GR_LINE_COLOR_FILL 0x0000ff7f
38 #define GR_LINE_COLOR_STROKE 0x9999007f
40 // screen pixels between knots when they snap:
41 #define SNAP_DIST 5
43 // absolute distance between gradient points for them to become a single dragger when the drag is created:
44 #define MERGE_DIST 0.1
46 // knot shapes corresponding to GrPoint enum
47 SPKnotShapeType gr_knot_shapes [] = {
48 SP_KNOT_SHAPE_SQUARE, //POINT_LG_P1
49 SP_KNOT_SHAPE_SQUARE,
50 SP_KNOT_SHAPE_DIAMOND,
51 SP_KNOT_SHAPE_CIRCLE,
52 SP_KNOT_SHAPE_CIRCLE,
53 SP_KNOT_SHAPE_CROSS // POINT_RG_FOCUS
54 };
56 const gchar *gr_knot_descr [] = {
57 N_("Linear gradient <b>start</b>"), //POINT_LG_P1
58 N_("Linear gradient <b>end</b>"),
59 N_("Radial gradient <b>center</b>"),
60 N_("Radial gradient <b>radius</b>"),
61 N_("Radial gradient <b>radius</b>"),
62 N_("Radial gradient <b>focus</b>") // POINT_RG_FOCUS
63 };
65 static void
66 gr_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
67 {
68 GrDrag *drag = (GrDrag *) data;
69 drag->updateDraggers ();
70 drag->updateLines ();
71 drag->updateLevels ();
72 }
74 static void
75 gr_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
76 {
77 GrDrag *drag = (GrDrag *) data;
78 if (drag->local_change) {
79 drag->local_change = false;
80 } else {
81 drag->updateDraggers ();
82 }
83 drag->updateLines ();
84 drag->updateLevels ();
85 }
87 /**
88 When a _query_style_signal is received, check that \a property requests fill/stroke (otherwise
89 skip), and fill the \a style with the averaged color of all draggables of the selected dragger, if
90 any.
91 */
92 int
93 gr_drag_style_query (SPStyle *style, int property, gpointer data)
94 {
95 GrDrag *drag = (GrDrag *) data;
97 if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE) {
98 return QUERY_STYLE_NOTHING;
99 }
101 if (!drag->selected) {
102 return QUERY_STYLE_NOTHING;
103 } else {
104 int ret = QUERY_STYLE_NOTHING;
106 float cf[4];
107 cf[0] = cf[1] = cf[2] = cf[3] = 0;
109 int count = 0;
111 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
112 GrDraggable *draggable = (GrDraggable *) i->data;
114 if (ret == QUERY_STYLE_NOTHING) {
115 ret = QUERY_STYLE_SINGLE;
116 } else if (ret == QUERY_STYLE_SINGLE) {
117 ret = QUERY_STYLE_MULTIPLE_AVERAGED;
118 }
120 guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_num, draggable->fill_or_stroke);
121 cf[0] += SP_RGBA32_R_F (c);
122 cf[1] += SP_RGBA32_G_F (c);
123 cf[2] += SP_RGBA32_B_F (c);
124 cf[3] += SP_RGBA32_A_F (c);
126 count ++;
127 }
129 if (count) {
130 cf[0] /= count;
131 cf[1] /= count;
132 cf[2] /= count;
133 cf[3] /= count;
135 // set both fill and stroke with our stop-color and stop-opacity
136 sp_color_set_rgb_float((SPColor *) &style->fill.value.color, cf[0], cf[1], cf[2]);
137 style->fill.set = TRUE;
138 style->fill.type = SP_PAINT_TYPE_COLOR;
139 sp_color_set_rgb_float((SPColor *) &style->stroke.value.color, cf[0], cf[1], cf[2]);
140 style->stroke.set = TRUE;
141 style->stroke.type = SP_PAINT_TYPE_COLOR;
143 style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
144 style->fill_opacity.set = TRUE;
145 style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
146 style->stroke_opacity.set = TRUE;
147 }
149 return ret;
150 }
151 }
153 bool
154 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
155 {
156 GrDrag *drag = (GrDrag *) data;
158 if (!drag->selected)
159 return false;
161 SPCSSAttr *stop = sp_repr_css_attr_new ();
163 // See if the css contains interesting properties, and if so, translate them into the format
164 // acceptable for gradient stops
166 // any of color properties, in order of increasing priority:
167 if (css->attribute("flood-color"))
168 sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color"));
170 if (css->attribute("lighting-color"))
171 sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color"));
173 if (css->attribute("color"))
174 sp_repr_css_set_property (stop, "stop-color", css->attribute("color"));
176 if (css->attribute("stroke") && strcmp(css->attribute("stroke"), "none"))
177 sp_repr_css_set_property (stop, "stop-color", css->attribute("stroke"));
179 if (css->attribute("fill") && strcmp(css->attribute("fill"), "none"))
180 sp_repr_css_set_property (stop, "stop-color", css->attribute("fill"));
182 if (css->attribute("stop-color"))
183 sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color"));
185 // any of opacity properties, in order of increasing priority:
186 if (css->attribute("flood-opacity"))
187 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("flood-color"));
189 if (css->attribute("opacity")) // TODO: multiply
190 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("color"));
192 if (css->attribute("stroke-opacity")) // TODO: multiply
193 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stroke-opacity"));
195 if (css->attribute("fill-opacity")) // TODO: multiply
196 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("fill-opacity"));
198 if ((css->attribute("fill") && !strcmp(css->attribute("fill"), "none")) ||
199 (css->attribute("stroke") && !strcmp(css->attribute("stroke"), "none")))
200 sp_repr_css_set_property (stop, "stop-opacity", "0"); // if set to none, don't change color, set opacity to 0
202 if (css->attribute("stop-opacity"))
203 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stop-opacity"));
205 if (!stop->attributeList()) { // nothing for us here, pass it on
206 sp_repr_css_attr_unref(stop);
207 return false;
208 }
210 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
211 GrDraggable *draggable = (GrDraggable *) i->data;
213 drag->local_change = true;
214 sp_item_gradient_stop_set_style (draggable->item, draggable->point_num, draggable->fill_or_stroke, stop);
215 }
217 //sp_repr_css_print(stop);
218 sp_repr_css_attr_unref(stop);
219 return true;
220 }
222 GrDrag::GrDrag(SPDesktop *desktop) {
224 this->desktop = desktop;
226 this->selection = SP_DT_SELECTION(desktop);
228 this->draggers = NULL;
229 this->lines = NULL;
230 this->selected = NULL;
232 this->hor_levels.clear();
233 this->vert_levels.clear();
235 this->local_change = false;
237 this->sel_changed_connection = this->selection->connectChanged(
238 sigc::bind (
239 sigc::ptr_fun(&gr_drag_sel_changed),
240 (gpointer)this )
242 );
243 this->sel_modified_connection = this->selection->connectModified(
244 sigc::bind(
245 sigc::ptr_fun(&gr_drag_sel_modified),
246 (gpointer)this )
247 );
249 this->style_set_connection = this->desktop->connectSetStyle(
250 sigc::bind(
251 sigc::ptr_fun(&gr_drag_style_set),
252 (gpointer)this )
253 );
255 this->style_query_connection = this->desktop->connectQueryStyle(
256 sigc::bind(
257 sigc::ptr_fun(&gr_drag_style_query),
258 (gpointer)this )
259 );
261 this->updateDraggers ();
262 this->updateLines ();
263 this->updateLevels ();
265 if (desktop->gr_item) {
266 this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_num, desktop->gr_fill_or_stroke));
267 }
268 }
270 GrDrag::~GrDrag()
271 {
272 this->sel_changed_connection.disconnect();
273 this->sel_modified_connection.disconnect();
274 this->style_set_connection.disconnect();
275 this->style_query_connection.disconnect();
277 if (this->selected) {
278 GrDraggable *draggable = (GrDraggable *) this->selected->draggables->data;
279 desktop->gr_item = draggable->item;
280 desktop->gr_point_num = draggable->point_num;
281 desktop->gr_fill_or_stroke = draggable->fill_or_stroke;
282 } else {
283 desktop->gr_item = NULL;
284 desktop->gr_point_num = 0;
285 desktop->gr_fill_or_stroke = true;
286 }
288 for (GList *l = this->draggers; l != NULL; l = l->next) {
289 delete ((GrDragger *) l->data);
290 }
291 g_list_free (this->draggers);
292 this->draggers = NULL;
293 this->selected = NULL;
295 for (GSList *l = this->lines; l != NULL; l = l->next) {
296 gtk_object_destroy( GTK_OBJECT (l->data));
297 }
298 g_slist_free (this->lines);
299 this->lines = NULL;
300 }
302 GrDraggable::GrDraggable (SPItem *item, guint point_num, bool fill_or_stroke)
303 {
304 this->item = item;
305 this->point_num = point_num;
306 this->fill_or_stroke = fill_or_stroke;
308 g_object_ref (G_OBJECT (this->item));
309 }
311 GrDraggable::~GrDraggable ()
312 {
313 g_object_unref (G_OBJECT (this->item));
314 }
316 NR::Point *
317 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
318 {
319 double r = NR::L2 (p - o);
320 if (r < 1e-3)
321 return NULL;
322 double angle = NR::atan2 (p - o);
323 // snap angle to snaps increments, starting from initial:
324 double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap;
325 // calculate the new position and subtract p to get the vector:
326 return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p);
327 }
329 static void
330 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
331 {
332 GrDragger *dragger = (GrDragger *) data;
334 NR::Point p = *ppointer;
336 // FIXME: take from prefs
337 double snap_dist = SNAP_DIST / dragger->parent->desktop->current_zoom();
339 if (state & GDK_SHIFT_MASK) {
340 // with Shift; unsnap if we carry more than one draggable
341 if (dragger->draggables && dragger->draggables->next) {
342 // create a new dragger
343 GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, NULL);
344 dragger->parent->draggers = g_list_prepend (dragger->parent->draggers, dr_new);
345 // relink to it all but the first draggable in the list
346 for (GSList const* i = dragger->draggables->next; i != NULL; i = i->next) {
347 GrDraggable *draggable = (GrDraggable *) i->data;
348 dr_new->addDraggable (draggable);
349 }
350 dr_new->updateKnotShape();
351 g_slist_free (dragger->draggables->next);
352 dragger->draggables->next = NULL;
353 dragger->updateKnotShape();
354 dragger->updateTip();
355 }
356 } else if (!(state & GDK_CONTROL_MASK)) {
357 // without Shift or Ctrl; see if we need to snap to another dragger
358 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
359 GrDragger *d_new = (GrDragger *) di->data;
360 if (dragger->mayMerge(d_new) && NR::L2 (d_new->point - p) < snap_dist) {
362 // Merge draggers:
363 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
364 GrDraggable *draggable = (GrDraggable *) i->data;
365 // copy draggable to d_new:
366 GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_num, draggable->fill_or_stroke);
367 d_new->addDraggable (da_new);
368 }
370 // unlink and delete this dragger
371 dragger->parent->draggers = g_list_remove (dragger->parent->draggers, dragger);
372 delete dragger;
374 // update the new merged dragger
375 d_new->fireDraggables(true, false, true);
376 d_new->parent->updateLines();
377 d_new->parent->setSelected (d_new);
378 d_new->updateKnotShape ();
379 d_new->updateTip ();
380 d_new->updateDependencies(true);
381 sp_document_done (SP_DT_DOCUMENT (d_new->parent->desktop));
382 return;
383 }
384 }
385 }
387 if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) {
388 // See if we need to snap to any of the levels
389 for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) {
390 if (fabs(p[NR::Y] - dragger->parent->hor_levels[i]) < snap_dist) {
391 p[NR::Y] = dragger->parent->hor_levels[i];
392 sp_knot_moveto (knot, &p);
393 }
394 }
395 for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) {
396 if (fabs(p[NR::X] - dragger->parent->vert_levels[i]) < snap_dist) {
397 p[NR::X] = dragger->parent->vert_levels[i];
398 sp_knot_moveto (knot, &p);
399 }
400 }
401 }
403 if (state & GDK_CONTROL_MASK) {
404 unsigned snaps = abs(prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12));
405 /* 0 means no snapping. */
407 // This list will store snap vectors from all draggables of dragger
408 GSList *snap_vectors = NULL;
410 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) {
411 GrDraggable *draggable = (GrDraggable *) i->data;
413 NR::Point *dr_snap = NULL;
415 if (draggable->point_num == POINT_LG_P1 || draggable->point_num == POINT_LG_P2) {
416 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
417 GrDragger *d_new = (GrDragger *) di->data;
418 if (d_new == dragger)
419 continue;
420 if (d_new->isA (draggable->item,
421 draggable->point_num == POINT_LG_P1? POINT_LG_P2 : POINT_LG_P1,
422 draggable->fill_or_stroke)) {
423 // found the other end of the linear gradient;
424 if (state & GDK_SHIFT_MASK) {
425 // moving linear around center
426 NR::Point center = NR::Point (0.5*(d_new->point + dragger->point));
427 dr_snap = ¢er;
428 } else {
429 // moving linear around the other end
430 dr_snap = &d_new->point;
431 }
432 }
433 }
434 } else if (draggable->point_num == POINT_RG_R1 || draggable->point_num == POINT_RG_R2 || draggable->point_num == POINT_RG_FOCUS) {
435 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
436 GrDragger *d_new = (GrDragger *) di->data;
437 if (d_new == dragger)
438 continue;
439 if (d_new->isA (draggable->item,
440 POINT_RG_CENTER,
441 draggable->fill_or_stroke)) {
442 // found the center of the radial gradient;
443 dr_snap = &(d_new->point);
444 }
445 }
446 } else if (draggable->point_num == POINT_RG_CENTER) {
447 // radial center snaps to hor/vert relative to its original position
448 dr_snap = &(dragger->point_original);
449 }
451 NR::Point *snap_vector = NULL;
452 if (dr_snap) {
453 if (state & GDK_MOD1_MASK) {
454 // with Alt, snap to the original angle and its perpendiculars
455 snap_vector = get_snap_vector (p, *dr_snap, M_PI/2, NR::atan2 (dragger->point_original - *dr_snap));
456 } else {
457 // with Ctrl, snap to M_PI/snaps
458 snap_vector = get_snap_vector (p, *dr_snap, M_PI/snaps, 0);
459 }
460 }
461 if (snap_vector) {
462 snap_vectors = g_slist_prepend (snap_vectors, snap_vector);
463 }
464 }
466 // Move by the smallest of snap vectors:
467 NR::Point move(9999, 9999);
468 for (GSList const *i = snap_vectors; i != NULL; i = i->next) {
469 NR::Point *snap_vector = (NR::Point *) i->data;
470 if (NR::L2(*snap_vector) < NR::L2(move))
471 move = *snap_vector;
472 }
473 if (move[NR::X] < 9999) {
474 p += move;
475 sp_knot_moveto (knot, &p);
476 }
477 }
479 dragger->point = p;
481 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
482 dragger->fireDraggables (false, true);
483 } else {
484 dragger->fireDraggables (false);
485 }
487 dragger->updateDependencies(false);
488 }
490 /**
491 Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
492 */
493 static void
494 gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
495 {
496 GrDragger *dragger = (GrDragger *) data;
498 dragger->point_original = dragger->point = knot->pos;
500 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
501 dragger->fireDraggables (true, true);
502 } else {
503 dragger->fireDraggables (true);
504 }
506 // make this dragger selected
507 dragger->parent->setSelected (dragger);
509 dragger->updateDependencies(true);
511 // we did an undoable action
512 sp_document_done (SP_DT_DOCUMENT (dragger->parent->desktop));
513 }
515 /**
516 Called when a dragger knot is clicked; selects the dragger
517 */
518 static void
519 gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
520 {
521 GrDragger *dragger = (GrDragger *) data;
523 dragger->point_original = dragger->point;
525 dragger->parent->setSelected (dragger);
526 }
528 /**
529 Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
530 */
531 static void
532 gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data)
533 {
534 GrDragger *dragger = (GrDragger *) data;
536 dragger->point_original = dragger->point;
538 if (dragger->draggables == NULL)
539 return;
541 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
542 sp_item_gradient_edit_stop (draggable->item, draggable->point_num, draggable->fill_or_stroke);
543 }
545 /**
546 Act upon all draggables of the dragger, setting them to the dragger's point
547 */
548 void
549 GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus)
550 {
551 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
552 GrDraggable *draggable = (GrDraggable *) i->data;
554 // set local_change flag so that selection_changed callback does not regenerate draggers
555 this->parent->local_change = true;
557 // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
558 // to the center, unless it's the first update upon merge when we must snap it to the point
559 if (merging_focus ||
560 !(draggable->point_num == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->fill_or_stroke)))
561 sp_item_gradient_set_coords (draggable->item, draggable->point_num, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
562 }
563 }
565 /**
566 Checks if the dragger has a draggable with this point_num
567 */
568 bool
569 GrDragger::isA (guint point_num)
570 {
571 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
572 GrDraggable *draggable = (GrDraggable *) i->data;
573 if (draggable->point_num == point_num) {
574 return true;
575 }
576 }
577 return false;
578 }
580 /**
581 Checks if the dragger has a draggable with this item, point_num, fill_or_stroke
582 */
583 bool
584 GrDragger::isA (SPItem *item, guint point_num, bool fill_or_stroke)
585 {
586 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
587 GrDraggable *draggable = (GrDraggable *) i->data;
588 if (draggable->point_num == point_num && draggable->item == item && draggable->fill_or_stroke == fill_or_stroke) {
589 return true;
590 }
591 }
592 return false;
593 }
595 bool
596 GrDraggable::mayMerge (GrDraggable *da2)
597 {
598 if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
599 // we must not merge the points of the same gradient!
600 if (!((this->point_num == POINT_RG_FOCUS && da2->point_num == POINT_RG_CENTER) ||
601 (this->point_num == POINT_RG_CENTER && da2->point_num == POINT_RG_FOCUS))) {
602 // except that we can snap center and focus together
603 return false;
604 }
605 }
606 return true;
607 }
609 bool
610 GrDragger::mayMerge (GrDragger *other)
611 {
612 if (this == other)
613 return false;
615 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
616 GrDraggable *da1 = (GrDraggable *) i->data;
617 for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other
618 GrDraggable *da2 = (GrDraggable *) j->data;
619 if (!da1->mayMerge(da2))
620 return false;
621 }
622 }
623 return true;
624 }
626 bool
627 GrDragger::mayMerge (GrDraggable *da2)
628 {
629 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
630 GrDraggable *da1 = (GrDraggable *) i->data;
631 if (!da1->mayMerge(da2))
632 return false;
633 }
634 return true;
635 }
637 /**
638 Updates the statusbar tip of the dragger knot, based on its draggables
639 */
640 void
641 GrDragger::updateTip ()
642 {
643 if (this->knot && this->knot->tip) {
644 g_free (this->knot->tip);
645 this->knot->tip = NULL;
646 }
648 if (g_slist_length (this->draggables) == 1) {
649 GrDraggable *draggable = (GrDraggable *) this->draggables->data;
650 char *item_desc = sp_item_description(draggable->item);
651 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"),
652 _(gr_knot_descr[draggable->point_num]),
653 item_desc,
654 draggable->fill_or_stroke == false ? _(" (stroke)") : "");
655 g_free(item_desc);
656 } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
657 this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
658 } else {
659 int length = g_slist_length (this->draggables);
660 this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
661 "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
662 length),
663 length);
664 }
665 }
667 /**
668 Adds a draggable to the dragger
669 */
670 void
671 GrDragger::updateKnotShape ()
672 {
673 if (!draggables)
674 return;
675 GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
676 g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_num], NULL);
677 }
679 /**
680 Adds a draggable to the dragger
681 */
682 void
683 GrDragger::addDraggable (GrDraggable *draggable)
684 {
685 this->draggables = g_slist_prepend (this->draggables, draggable);
687 this->updateTip();
688 }
691 /**
692 Moves this dragger to the point of the given draggable, acting upon all other draggables
693 */
694 void
695 GrDragger::moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
696 {
697 this->point = sp_item_gradient_get_coords (item, point_num, fill_or_stroke);
698 this->point_original = this->point;
700 sp_knot_moveto (this->knot, &(this->point));
702 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
703 GrDraggable *da = (GrDraggable *) i->data;
704 if (da->item == item && da->point_num == point_num && da->fill_or_stroke == fill_or_stroke) {
705 continue;
706 }
707 sp_item_gradient_set_coords (da->item, da->point_num, this->point, da->fill_or_stroke, write_repr, false);
708 }
709 // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
710 }
713 /**
714 Moves all draggables that depend on this one
715 */
716 void
717 GrDragger::updateDependencies (bool write_repr)
718 {
719 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
720 GrDraggable *draggable = (GrDraggable *) i->data;
721 switch (draggable->point_num) {
722 case POINT_LG_P1:
723 // the other point is dependent only when dragging with ctrl+shift
724 this->moveOtherToDraggable (draggable->item, POINT_LG_P2, draggable->fill_or_stroke, write_repr);
725 break;
726 case POINT_LG_P2:
727 this->moveOtherToDraggable (draggable->item, POINT_LG_P1, draggable->fill_or_stroke, write_repr);
728 break;
729 case POINT_RG_R2:
730 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
731 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
732 break;
733 case POINT_RG_R1:
734 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
735 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
736 break;
737 case POINT_RG_CENTER:
738 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
739 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
740 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
741 break;
742 case POINT_RG_FOCUS:
743 // nothing can depend on that
744 break;
745 default:
746 break;
747 }
748 }
749 }
753 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
754 {
755 this->draggables = NULL;
757 this->parent = parent;
759 this->point = p;
760 this->point_original = p;
762 // create the knot
763 this->knot = sp_knot_new (parent->desktop, NULL);
764 this->knot->setMode(SP_KNOT_MODE_XOR);
765 this->knot->setFill(GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL);
766 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
767 sp_knot_update_ctrl(this->knot);
769 // move knot to the given point
770 sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
771 sp_knot_show (this->knot);
773 // connect knot's signals
774 this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
775 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
776 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
777 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
779 // add the initial draggable
780 if (draggable)
781 this->addDraggable (draggable);
782 updateKnotShape();
783 }
785 GrDragger::~GrDragger ()
786 {
787 // unselect if it was selected
788 if (this->parent->selected == this)
789 this->parent->setSelected (NULL);
791 /* unref should call destroy */
792 g_object_unref (G_OBJECT (this->knot));
794 // delete all draggables
795 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
796 delete ((GrDraggable *) i->data);
797 }
798 g_slist_free (this->draggables);
799 this->draggables = NULL;
800 }
802 /**
803 Select the dragger which has the given draggable.
804 */
805 GrDragger *
806 GrDrag::getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke)
807 {
808 for (GList const* i = this->draggers; i != NULL; i = i->next) {
809 GrDragger *dragger = (GrDragger *) i->data;
810 for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
811 GrDraggable *da2 = (GrDraggable *) j->data;
812 if (da2->item == item && da2->point_num == point_num && da2->fill_or_stroke == fill_or_stroke) {
813 return (dragger);
814 }
815 }
816 }
817 return NULL;
818 }
821 void
822 GrDragger::moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
823 {
824 GrDragger *d = this->parent->getDraggerFor (item, point_num, fill_or_stroke);
825 if (d && d != this) {
826 d->moveThisToDraggable (item, point_num, fill_or_stroke, write_repr);
827 }
828 }
831 /**
832 Set selected dragger
833 */
834 void
835 GrDrag::setSelected (GrDragger *dragger)
836 {
837 if (this->selected) {
838 this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
839 g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
840 }
841 if (dragger) {
842 dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
843 g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
844 }
845 this->selected = dragger;
847 this->desktop->emitToolSubselectionChanged((gpointer) dragger);
848 }
850 /**
851 Create a line from p1 to p2 and add it to the lines list
852 */
853 void
854 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
855 {
856 SPCanvasItem *line = sp_canvas_item_new(SP_DT_CONTROLS(this->desktop),
857 SP_TYPE_CTRLLINE, NULL);
858 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
859 if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
860 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
861 sp_canvas_item_show (line);
862 this->lines = g_slist_append (this->lines, line);
863 }
865 /**
866 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
867 new dragger and add it to draggers list
868 */
869 void
870 GrDrag::addDragger (GrDraggable *draggable)
871 {
872 NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_num, draggable->fill_or_stroke);
874 for (GList *i = this->draggers; i != NULL; i = i->next) {
875 GrDragger *dragger = (GrDragger *) i->data;
876 if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
877 // distance is small, merge this draggable into dragger, no need to create new dragger
878 dragger->addDraggable (draggable);
879 dragger->updateKnotShape();
880 return;
881 }
882 }
884 GrDragger *new_dragger = new GrDragger(this, p, draggable);
885 this->draggers = g_list_prepend (this->draggers, new_dragger);
886 }
888 /**
889 Add draggers for the radial gradient rg on item
890 */
891 void
892 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
893 {
894 addDragger (new GrDraggable (item, POINT_RG_CENTER, fill_or_stroke));
895 addDragger (new GrDraggable (item, POINT_RG_FOCUS, fill_or_stroke));
896 addDragger (new GrDraggable (item, POINT_RG_R1, fill_or_stroke));
897 addDragger (new GrDraggable (item, POINT_RG_R2, fill_or_stroke));
898 }
900 /**
901 Add draggers for the linear gradient lg on item
902 */
903 void
904 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
905 {
906 addDragger (new GrDraggable (item, POINT_LG_P1, fill_or_stroke));
907 addDragger (new GrDraggable (item, POINT_LG_P2, fill_or_stroke));
908 }
910 /**
911 Artificially grab the knot of the dragger with this draggable; used by the gradient context
912 */
913 void
914 GrDrag::grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime)
915 {
916 GrDragger *dragger = getDraggerFor (item, point_num, fill_or_stroke);
917 if (dragger) {
918 sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
919 }
920 }
922 /**
923 Regenerates the draggers list from the current selection; is called when selection is changed or
924 modified, also when a radial dragger needs to update positions of other draggers in the gradient
925 */
926 void
927 GrDrag::updateDraggers ()
928 {
929 // delete old draggers and deselect
930 for (GList const* i = this->draggers; i != NULL; i = i->next) {
931 delete ((GrDragger *) i->data);
932 }
933 g_list_free (this->draggers);
934 this->draggers = NULL;
935 this->selected = NULL;
937 g_return_if_fail (this->selection != NULL);
939 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
941 SPItem *item = SP_ITEM(i->data);
942 SPStyle *style = SP_OBJECT_STYLE (item);
944 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
945 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
946 if (SP_IS_LINEARGRADIENT (server)) {
947 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
948 } else if (SP_IS_RADIALGRADIENT (server)) {
949 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
950 }
951 }
953 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
954 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
955 if (SP_IS_LINEARGRADIENT (server)) {
956 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
957 } else if (SP_IS_RADIALGRADIENT (server)) {
958 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
959 }
960 }
963 }
964 }
966 /**
967 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
968 lines are always in sync with the actual gradient
969 */
970 void
971 GrDrag::updateLines ()
972 {
973 // delete old lines
974 for (GSList const *i = this->lines; i != NULL; i = i->next) {
975 gtk_object_destroy( GTK_OBJECT (i->data));
976 }
977 g_slist_free (this->lines);
978 this->lines = NULL;
980 g_return_if_fail (this->selection != NULL);
982 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
984 SPItem *item = SP_ITEM(i->data);
986 SPStyle *style = SP_OBJECT_STYLE (item);
988 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
989 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
990 if (SP_IS_LINEARGRADIENT (server)) {
991 this->addLine (sp_item_gradient_get_coords (item, POINT_LG_P1, true), sp_item_gradient_get_coords (item, POINT_LG_P2, true), GR_LINE_COLOR_FILL);
992 } else if (SP_IS_RADIALGRADIENT (server)) {
993 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, true);
994 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, true), GR_LINE_COLOR_FILL);
995 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, true), GR_LINE_COLOR_FILL);
996 }
997 }
999 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1000 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1001 if (SP_IS_LINEARGRADIENT (server)) {
1002 this->addLine (sp_item_gradient_get_coords (item, POINT_LG_P1, false), sp_item_gradient_get_coords (item, POINT_LG_P2, false), GR_LINE_COLOR_STROKE);
1003 } else if (SP_IS_RADIALGRADIENT (server)) {
1004 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, false);
1005 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, false), GR_LINE_COLOR_STROKE);
1006 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, false), GR_LINE_COLOR_STROKE);
1007 }
1008 }
1009 }
1010 }
1012 /**
1013 Regenerates the levels list from the current selection
1014 */
1015 void
1016 GrDrag::updateLevels ()
1017 {
1018 hor_levels.clear();
1019 vert_levels.clear();
1021 g_return_if_fail (this->selection != NULL);
1023 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1024 SPItem *item = SP_ITEM(i->data);
1025 NR::Rect rect = sp_item_bbox_desktop (item);
1026 // Remember the edges of the bbox and the center axis
1027 hor_levels.push_back(rect.min()[NR::Y]);
1028 hor_levels.push_back(rect.max()[NR::Y]);
1029 hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1030 vert_levels.push_back(rect.min()[NR::X]);
1031 vert_levels.push_back(rect.max()[NR::X]);
1032 vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1033 }
1034 }
1036 void
1037 GrDrag::selected_reverse_vector ()
1038 {
1039 if (selected == NULL)
1040 return;
1042 for (GSList const* i = selected->draggables; i != NULL; i = i->next) {
1043 GrDraggable *draggable = (GrDraggable *) i->data;
1045 sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1046 }
1047 }
1049 void
1050 GrDrag::selected_move (double x, double y)
1051 {
1052 if (selected == NULL)
1053 return;
1055 selected->point += NR::Point (x, y);
1056 selected->point_original = selected->point;
1057 sp_knot_moveto (selected->knot, &(selected->point));
1059 selected->fireDraggables (true);
1061 selected->updateDependencies(true);
1063 // we did an undoable action
1064 sp_document_done (SP_DT_DOCUMENT (desktop));
1065 }
1067 void
1068 GrDrag::selected_move_screen (double x, double y)
1069 {
1070 gdouble zoom = desktop->current_zoom();
1071 gdouble zx = x / zoom;
1072 gdouble zy = y / zoom;
1074 selected_move (zx, zy);
1075 }
1077 void
1078 GrDrag::select_next ()
1079 {
1080 if (selected == NULL || g_list_find(draggers, selected)->next == NULL) {
1081 if (draggers)
1082 setSelected ((GrDragger *) draggers->data);
1083 } else {
1084 setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data);
1085 }
1086 }
1088 void
1089 GrDrag::select_prev ()
1090 {
1091 if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) {
1092 if (draggers)
1093 setSelected ((GrDragger *) g_list_last (draggers)->data);
1094 } else {
1095 setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data);
1096 }
1097 }
1100 /*
1101 Local Variables:
1102 mode:c++
1103 c-file-style:"stroustrup"
1104 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1105 indent-tabs-mode:nil
1106 fill-column:99
1107 End:
1108 */
1109 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :