e09bedeee68cb46d88540acc12553fd59fadef1d
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 GrPoint enum
51 SPKnotShapeType gr_knot_shapes [] = {
52 SP_KNOT_SHAPE_SQUARE, //POINT_LG_P1
53 SP_KNOT_SHAPE_SQUARE,
54 SP_KNOT_SHAPE_DIAMOND,
55 SP_KNOT_SHAPE_CIRCLE,
56 SP_KNOT_SHAPE_CIRCLE,
57 SP_KNOT_SHAPE_CROSS // POINT_RG_FOCUS
58 };
60 const gchar *gr_knot_descr [] = {
61 N_("Linear gradient <b>start</b>"), //POINT_LG_P1
62 N_("Linear gradient <b>end</b>"),
63 N_("Radial gradient <b>center</b>"),
64 N_("Radial gradient <b>radius</b>"),
65 N_("Radial gradient <b>radius</b>"),
66 N_("Radial gradient <b>focus</b>") // POINT_RG_FOCUS
67 };
69 static void
70 gr_drag_sel_changed(Inkscape::Selection *selection, gpointer data)
71 {
72 GrDrag *drag = (GrDrag *) data;
73 drag->updateDraggers ();
74 drag->updateLines ();
75 drag->updateLevels ();
76 }
78 static void
79 gr_drag_sel_modified (Inkscape::Selection *selection, guint flags, gpointer data)
80 {
81 GrDrag *drag = (GrDrag *) data;
82 if (drag->local_change) {
83 drag->local_change = false;
84 } else {
85 drag->updateDraggers ();
86 }
87 drag->updateLines ();
88 drag->updateLevels ();
89 }
91 /**
92 When a _query_style_signal is received, check that \a property requests fill/stroke (otherwise
93 skip), and fill the \a style with the averaged color of all draggables of the selected dragger, if
94 any.
95 */
96 int
97 gr_drag_style_query (SPStyle *style, int property, gpointer data)
98 {
99 GrDrag *drag = (GrDrag *) data;
101 if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE) {
102 return QUERY_STYLE_NOTHING;
103 }
105 if (!drag->selected) {
106 return QUERY_STYLE_NOTHING;
107 } else {
108 int ret = QUERY_STYLE_NOTHING;
110 float cf[4];
111 cf[0] = cf[1] = cf[2] = cf[3] = 0;
113 int count = 0;
115 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
116 GrDraggable *draggable = (GrDraggable *) i->data;
118 if (ret == QUERY_STYLE_NOTHING) {
119 ret = QUERY_STYLE_SINGLE;
120 } else if (ret == QUERY_STYLE_SINGLE) {
121 ret = QUERY_STYLE_MULTIPLE_AVERAGED;
122 }
124 guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_num, draggable->fill_or_stroke);
125 cf[0] += SP_RGBA32_R_F (c);
126 cf[1] += SP_RGBA32_G_F (c);
127 cf[2] += SP_RGBA32_B_F (c);
128 cf[3] += SP_RGBA32_A_F (c);
130 count ++;
131 }
133 if (count) {
134 cf[0] /= count;
135 cf[1] /= count;
136 cf[2] /= count;
137 cf[3] /= count;
139 // set both fill and stroke with our stop-color and stop-opacity
140 sp_color_set_rgb_float((SPColor *) &style->fill.value.color, cf[0], cf[1], cf[2]);
141 style->fill.set = TRUE;
142 style->fill.type = SP_PAINT_TYPE_COLOR;
143 sp_color_set_rgb_float((SPColor *) &style->stroke.value.color, cf[0], cf[1], cf[2]);
144 style->stroke.set = TRUE;
145 style->stroke.type = SP_PAINT_TYPE_COLOR;
147 style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
148 style->fill_opacity.set = TRUE;
149 style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]);
150 style->stroke_opacity.set = TRUE;
151 }
153 return ret;
154 }
155 }
157 bool
158 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
159 {
160 GrDrag *drag = (GrDrag *) data;
162 if (!drag->selected)
163 return false;
165 SPCSSAttr *stop = sp_repr_css_attr_new ();
167 // See if the css contains interesting properties, and if so, translate them into the format
168 // acceptable for gradient stops
170 // any of color properties, in order of increasing priority:
171 if (css->attribute("flood-color"))
172 sp_repr_css_set_property (stop, "stop-color", css->attribute("flood-color"));
174 if (css->attribute("lighting-color"))
175 sp_repr_css_set_property (stop, "stop-color", css->attribute("lighting-color"));
177 if (css->attribute("color"))
178 sp_repr_css_set_property (stop, "stop-color", css->attribute("color"));
180 if (css->attribute("stroke") && strcmp(css->attribute("stroke"), "none"))
181 sp_repr_css_set_property (stop, "stop-color", css->attribute("stroke"));
183 if (css->attribute("fill") && strcmp(css->attribute("fill"), "none"))
184 sp_repr_css_set_property (stop, "stop-color", css->attribute("fill"));
186 if (css->attribute("stop-color"))
187 sp_repr_css_set_property (stop, "stop-color", css->attribute("stop-color"));
189 // any of opacity properties, in order of increasing priority:
190 if (css->attribute("flood-opacity"))
191 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("flood-color"));
193 if (css->attribute("opacity")) // TODO: multiply
194 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("color"));
196 if (css->attribute("stroke-opacity")) // TODO: multiply
197 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stroke-opacity"));
199 if (css->attribute("fill-opacity")) // TODO: multiply
200 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("fill-opacity"));
202 if ((css->attribute("fill") && !strcmp(css->attribute("fill"), "none")) ||
203 (css->attribute("stroke") && !strcmp(css->attribute("stroke"), "none")))
204 sp_repr_css_set_property (stop, "stop-opacity", "0"); // if set to none, don't change color, set opacity to 0
206 if (css->attribute("stop-opacity"))
207 sp_repr_css_set_property (stop, "stop-opacity", css->attribute("stop-opacity"));
209 if (!stop->attributeList()) { // nothing for us here, pass it on
210 sp_repr_css_attr_unref(stop);
211 return false;
212 }
214 for (GSList const* i = drag->selected->draggables; i != NULL; i = i->next) { // for all draggables of dragger
215 GrDraggable *draggable = (GrDraggable *) i->data;
217 drag->local_change = true;
218 sp_item_gradient_stop_set_style (draggable->item, draggable->point_num, draggable->fill_or_stroke, stop);
219 }
221 //sp_repr_css_print(stop);
222 sp_repr_css_attr_unref(stop);
223 return true;
224 }
226 GrDrag::GrDrag(SPDesktop *desktop) {
228 this->desktop = desktop;
230 this->selection = sp_desktop_selection(desktop);
232 this->draggers = NULL;
233 this->lines = NULL;
234 this->selected = NULL;
236 this->hor_levels.clear();
237 this->vert_levels.clear();
239 this->local_change = false;
241 this->sel_changed_connection = this->selection->connectChanged(
242 sigc::bind (
243 sigc::ptr_fun(&gr_drag_sel_changed),
244 (gpointer)this )
246 );
247 this->sel_modified_connection = this->selection->connectModified(
248 sigc::bind(
249 sigc::ptr_fun(&gr_drag_sel_modified),
250 (gpointer)this )
251 );
253 this->style_set_connection = this->desktop->connectSetStyle(
254 sigc::bind(
255 sigc::ptr_fun(&gr_drag_style_set),
256 (gpointer)this )
257 );
259 this->style_query_connection = this->desktop->connectQueryStyle(
260 sigc::bind(
261 sigc::ptr_fun(&gr_drag_style_query),
262 (gpointer)this )
263 );
265 this->updateDraggers ();
266 this->updateLines ();
267 this->updateLevels ();
269 if (desktop->gr_item) {
270 this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_num, desktop->gr_fill_or_stroke));
271 }
272 }
274 GrDrag::~GrDrag()
275 {
276 this->sel_changed_connection.disconnect();
277 this->sel_modified_connection.disconnect();
278 this->style_set_connection.disconnect();
279 this->style_query_connection.disconnect();
281 if (this->selected) {
282 GrDraggable *draggable = (GrDraggable *) this->selected->draggables->data;
283 desktop->gr_item = draggable->item;
284 desktop->gr_point_num = draggable->point_num;
285 desktop->gr_fill_or_stroke = draggable->fill_or_stroke;
286 } else {
287 desktop->gr_item = NULL;
288 desktop->gr_point_num = 0;
289 desktop->gr_fill_or_stroke = true;
290 }
292 for (GList *l = this->draggers; l != NULL; l = l->next) {
293 delete ((GrDragger *) l->data);
294 }
295 g_list_free (this->draggers);
296 this->draggers = NULL;
297 this->selected = NULL;
299 for (GSList *l = this->lines; l != NULL; l = l->next) {
300 gtk_object_destroy( GTK_OBJECT (l->data));
301 }
302 g_slist_free (this->lines);
303 this->lines = NULL;
304 }
306 GrDraggable::GrDraggable (SPItem *item, guint point_num, bool fill_or_stroke)
307 {
308 this->item = item;
309 this->point_num = point_num;
310 this->fill_or_stroke = fill_or_stroke;
312 g_object_ref (G_OBJECT (this->item));
313 }
315 GrDraggable::~GrDraggable ()
316 {
317 g_object_unref (G_OBJECT (this->item));
318 }
320 NR::Point *
321 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
322 {
323 double r = NR::L2 (p - o);
324 if (r < 1e-3)
325 return NULL;
326 double angle = NR::atan2 (p - o);
327 // snap angle to snaps increments, starting from initial:
328 double a_snapped = initial + floor((angle - initial)/snap + 0.5) * snap;
329 // calculate the new position and subtract p to get the vector:
330 return new NR::Point (o + r * NR::Point(cos(a_snapped), sin(a_snapped)) - p);
331 }
333 static void
334 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
335 {
336 GrDragger *dragger = (GrDragger *) data;
338 NR::Point p = *ppointer;
340 // FIXME: take from prefs
341 double snap_dist = SNAP_DIST / dragger->parent->desktop->current_zoom();
343 if (state & GDK_SHIFT_MASK) {
344 // with Shift; unsnap if we carry more than one draggable
345 if (dragger->draggables && dragger->draggables->next) {
346 // create a new dragger
347 GrDragger *dr_new = new GrDragger (dragger->parent, dragger->point, NULL);
348 dragger->parent->draggers = g_list_prepend (dragger->parent->draggers, dr_new);
349 // relink to it all but the first draggable in the list
350 for (GSList const* i = dragger->draggables->next; i != NULL; i = i->next) {
351 GrDraggable *draggable = (GrDraggable *) i->data;
352 dr_new->addDraggable (draggable);
353 }
354 dr_new->updateKnotShape();
355 g_slist_free (dragger->draggables->next);
356 dragger->draggables->next = NULL;
357 dragger->updateKnotShape();
358 dragger->updateTip();
359 }
360 } else if (!(state & GDK_CONTROL_MASK)) {
361 // without Shift or Ctrl; see if we need to snap to another dragger
362 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
363 GrDragger *d_new = (GrDragger *) di->data;
364 if (dragger->mayMerge(d_new) && NR::L2 (d_new->point - p) < snap_dist) {
366 // Merge draggers:
367 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
368 GrDraggable *draggable = (GrDraggable *) i->data;
369 // copy draggable to d_new:
370 GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_num, draggable->fill_or_stroke);
371 d_new->addDraggable (da_new);
372 }
374 // unlink and delete this dragger
375 dragger->parent->draggers = g_list_remove (dragger->parent->draggers, dragger);
376 delete dragger;
378 // update the new merged dragger
379 d_new->fireDraggables(true, false, true);
380 d_new->parent->updateLines();
381 d_new->parent->setSelected (d_new);
382 d_new->updateKnotShape ();
383 d_new->updateTip ();
384 d_new->updateDependencies(true);
385 sp_document_done (sp_desktop_document (d_new->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
386 _("Merge gradient handles"));
387 return;
388 }
389 }
390 }
392 if (!((state & GDK_SHIFT_MASK) || ((state & GDK_CONTROL_MASK) && (state & GDK_MOD1_MASK)))) {
393 // See if we need to snap to any of the levels
394 for (guint i = 0; i < dragger->parent->hor_levels.size(); i++) {
395 if (fabs(p[NR::Y] - dragger->parent->hor_levels[i]) < snap_dist) {
396 p[NR::Y] = dragger->parent->hor_levels[i];
397 sp_knot_moveto (knot, &p);
398 }
399 }
400 for (guint i = 0; i < dragger->parent->vert_levels.size(); i++) {
401 if (fabs(p[NR::X] - dragger->parent->vert_levels[i]) < snap_dist) {
402 p[NR::X] = dragger->parent->vert_levels[i];
403 sp_knot_moveto (knot, &p);
404 }
405 }
406 }
408 if (state & GDK_CONTROL_MASK) {
409 unsigned snaps = abs(prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12));
410 /* 0 means no snapping. */
412 // This list will store snap vectors from all draggables of dragger
413 GSList *snap_vectors = NULL;
415 for (GSList const* i = dragger->draggables; i != NULL; i = i->next) {
416 GrDraggable *draggable = (GrDraggable *) i->data;
418 NR::Point *dr_snap = NULL;
420 if (draggable->point_num == POINT_LG_P1 || draggable->point_num == POINT_LG_P2) {
421 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
422 GrDragger *d_new = (GrDragger *) di->data;
423 if (d_new == dragger)
424 continue;
425 if (d_new->isA (draggable->item,
426 draggable->point_num == POINT_LG_P1? POINT_LG_P2 : POINT_LG_P1,
427 draggable->fill_or_stroke)) {
428 // found the other end of the linear gradient;
429 if (state & GDK_SHIFT_MASK) {
430 // moving linear around center
431 NR::Point center = NR::Point (0.5*(d_new->point + dragger->point));
432 dr_snap = ¢er;
433 } else {
434 // moving linear around the other end
435 dr_snap = &d_new->point;
436 }
437 }
438 }
439 } else if (draggable->point_num == POINT_RG_R1 || draggable->point_num == POINT_RG_R2 || draggable->point_num == POINT_RG_FOCUS) {
440 for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
441 GrDragger *d_new = (GrDragger *) di->data;
442 if (d_new == dragger)
443 continue;
444 if (d_new->isA (draggable->item,
445 POINT_RG_CENTER,
446 draggable->fill_or_stroke)) {
447 // found the center of the radial gradient;
448 dr_snap = &(d_new->point);
449 }
450 }
451 } else if (draggable->point_num == POINT_RG_CENTER) {
452 // radial center snaps to hor/vert relative to its original position
453 dr_snap = &(dragger->point_original);
454 }
456 NR::Point *snap_vector = NULL;
457 if (dr_snap) {
458 if (state & GDK_MOD1_MASK) {
459 // with Alt, snap to the original angle and its perpendiculars
460 snap_vector = get_snap_vector (p, *dr_snap, M_PI/2, NR::atan2 (dragger->point_original - *dr_snap));
461 } else {
462 // with Ctrl, snap to M_PI/snaps
463 snap_vector = get_snap_vector (p, *dr_snap, M_PI/snaps, 0);
464 }
465 }
466 if (snap_vector) {
467 snap_vectors = g_slist_prepend (snap_vectors, snap_vector);
468 }
469 g_slist_free(snap_vectors);
470 }
472 // Move by the smallest of snap vectors:
473 NR::Point move(9999, 9999);
474 for (GSList const *i = snap_vectors; i != NULL; i = i->next) {
475 NR::Point *snap_vector = (NR::Point *) i->data;
476 if (NR::L2(*snap_vector) < NR::L2(move))
477 move = *snap_vector;
478 }
479 if (move[NR::X] < 9999) {
480 p += move;
481 sp_knot_moveto (knot, &p);
482 }
483 }
485 dragger->point = p;
487 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
488 dragger->fireDraggables (false, true);
489 } else {
490 dragger->fireDraggables (false);
491 }
493 dragger->updateDependencies(false);
494 }
496 static void
497 gr_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
498 {
499 GrDragger *dragger = (GrDragger *) data;
501 sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
502 }
504 /**
505 Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
506 */
507 static void
508 gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
509 {
510 GrDragger *dragger = (GrDragger *) data;
512 sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
514 dragger->point_original = dragger->point = knot->pos;
516 if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
517 dragger->fireDraggables (true, true);
518 } else {
519 dragger->fireDraggables (true);
520 }
522 // make this dragger selected
523 dragger->parent->setSelected (dragger);
525 dragger->updateDependencies(true);
527 // we did an undoable action
528 sp_document_done (sp_desktop_document (dragger->parent->desktop), SP_VERB_CONTEXT_GRADIENT,
529 _("Move gradient handle"));
530 }
532 /**
533 Called when a dragger knot is clicked; selects the dragger
534 */
535 static void
536 gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
537 {
538 GrDragger *dragger = (GrDragger *) data;
540 dragger->point_original = dragger->point;
542 dragger->parent->setSelected (dragger);
543 }
545 /**
546 Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
547 */
548 static void
549 gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data)
550 {
551 GrDragger *dragger = (GrDragger *) data;
553 dragger->point_original = dragger->point;
555 if (dragger->draggables == NULL)
556 return;
558 GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
559 sp_item_gradient_edit_stop (draggable->item, draggable->point_num, draggable->fill_or_stroke);
560 }
562 /**
563 Act upon all draggables of the dragger, setting them to the dragger's point
564 */
565 void
566 GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus)
567 {
568 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
569 GrDraggable *draggable = (GrDraggable *) i->data;
571 // set local_change flag so that selection_changed callback does not regenerate draggers
572 this->parent->local_change = true;
574 // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
575 // to the center, unless it's the first update upon merge when we must snap it to the point
576 if (merging_focus ||
577 !(draggable->point_num == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->fill_or_stroke)))
578 sp_item_gradient_set_coords (draggable->item, draggable->point_num, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
579 }
580 }
582 /**
583 Checks if the dragger has a draggable with this point_num
584 */
585 bool
586 GrDragger::isA (guint point_num)
587 {
588 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
589 GrDraggable *draggable = (GrDraggable *) i->data;
590 if (draggable->point_num == point_num) {
591 return true;
592 }
593 }
594 return false;
595 }
597 /**
598 Checks if the dragger has a draggable with this item, point_num, fill_or_stroke
599 */
600 bool
601 GrDragger::isA (SPItem *item, guint point_num, bool fill_or_stroke)
602 {
603 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
604 GrDraggable *draggable = (GrDraggable *) i->data;
605 if (draggable->point_num == point_num && draggable->item == item && draggable->fill_or_stroke == fill_or_stroke) {
606 return true;
607 }
608 }
609 return false;
610 }
612 bool
613 GrDraggable::mayMerge (GrDraggable *da2)
614 {
615 if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
616 // we must not merge the points of the same gradient!
617 if (!((this->point_num == POINT_RG_FOCUS && da2->point_num == POINT_RG_CENTER) ||
618 (this->point_num == POINT_RG_CENTER && da2->point_num == POINT_RG_FOCUS))) {
619 // except that we can snap center and focus together
620 return false;
621 }
622 }
623 return true;
624 }
626 bool
627 GrDragger::mayMerge (GrDragger *other)
628 {
629 if (this == other)
630 return false;
632 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
633 GrDraggable *da1 = (GrDraggable *) i->data;
634 for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other
635 GrDraggable *da2 = (GrDraggable *) j->data;
636 if (!da1->mayMerge(da2))
637 return false;
638 }
639 }
640 return true;
641 }
643 bool
644 GrDragger::mayMerge (GrDraggable *da2)
645 {
646 for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
647 GrDraggable *da1 = (GrDraggable *) i->data;
648 if (!da1->mayMerge(da2))
649 return false;
650 }
651 return true;
652 }
654 /**
655 Updates the statusbar tip of the dragger knot, based on its draggables
656 */
657 void
658 GrDragger::updateTip ()
659 {
660 if (this->knot && this->knot->tip) {
661 g_free (this->knot->tip);
662 this->knot->tip = NULL;
663 }
665 if (g_slist_length (this->draggables) == 1) {
666 GrDraggable *draggable = (GrDraggable *) this->draggables->data;
667 char *item_desc = sp_item_description(draggable->item);
668 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"),
669 _(gr_knot_descr[draggable->point_num]),
670 item_desc,
671 draggable->fill_or_stroke == false ? _(" (stroke)") : "");
672 g_free(item_desc);
673 } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
674 this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
675 } else {
676 int length = g_slist_length (this->draggables);
677 this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
678 "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
679 length),
680 length);
681 }
682 }
684 /**
685 Adds a draggable to the dragger
686 */
687 void
688 GrDragger::updateKnotShape ()
689 {
690 if (!draggables)
691 return;
692 GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
693 g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_num], NULL);
694 }
696 /**
697 Adds a draggable to the dragger
698 */
699 void
700 GrDragger::addDraggable (GrDraggable *draggable)
701 {
702 this->draggables = g_slist_prepend (this->draggables, draggable);
704 this->updateTip();
705 }
708 /**
709 Moves this dragger to the point of the given draggable, acting upon all other draggables
710 */
711 void
712 GrDragger::moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
713 {
714 this->point = sp_item_gradient_get_coords (item, point_num, fill_or_stroke);
715 this->point_original = this->point;
717 sp_knot_moveto (this->knot, &(this->point));
719 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
720 GrDraggable *da = (GrDraggable *) i->data;
721 if (da->item == item && da->point_num == point_num && da->fill_or_stroke == fill_or_stroke) {
722 continue;
723 }
724 sp_item_gradient_set_coords (da->item, da->point_num, this->point, da->fill_or_stroke, write_repr, false);
725 }
726 // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
727 }
730 /**
731 Moves all draggables that depend on this one
732 */
733 void
734 GrDragger::updateDependencies (bool write_repr)
735 {
736 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
737 GrDraggable *draggable = (GrDraggable *) i->data;
738 switch (draggable->point_num) {
739 case POINT_LG_P1:
740 // the other point is dependent only when dragging with ctrl+shift
741 this->moveOtherToDraggable (draggable->item, POINT_LG_P2, draggable->fill_or_stroke, write_repr);
742 break;
743 case POINT_LG_P2:
744 this->moveOtherToDraggable (draggable->item, POINT_LG_P1, draggable->fill_or_stroke, write_repr);
745 break;
746 case POINT_RG_R2:
747 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
748 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
749 break;
750 case POINT_RG_R1:
751 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
752 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
753 break;
754 case POINT_RG_CENTER:
755 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
756 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
757 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
758 break;
759 case POINT_RG_FOCUS:
760 // nothing can depend on that
761 break;
762 default:
763 break;
764 }
765 }
766 }
770 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
771 {
772 this->draggables = NULL;
774 this->parent = parent;
776 this->point = p;
777 this->point_original = p;
779 // create the knot
780 this->knot = sp_knot_new (parent->desktop, NULL);
781 this->knot->setMode(SP_KNOT_MODE_XOR);
782 this->knot->setFill(GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL);
783 this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
784 sp_knot_update_ctrl(this->knot);
786 // move knot to the given point
787 sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
788 sp_knot_show (this->knot);
790 // connect knot's signals
791 this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
792 g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
793 g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
794 g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (gr_knot_grabbed_handler), this);
795 g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
797 // add the initial draggable
798 if (draggable)
799 this->addDraggable (draggable);
800 updateKnotShape();
801 }
803 GrDragger::~GrDragger ()
804 {
805 // unselect if it was selected
806 if (this->parent->selected == this)
807 this->parent->setSelected (NULL);
809 // disconnect signals
810 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_moved_handler), this);
811 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_clicked_handler), this);
812 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_doubleclicked_handler), this);
813 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_grabbed_handler), this);
814 g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_ungrabbed_handler), this);
816 /* unref should call destroy */
817 g_object_unref (G_OBJECT (this->knot));
819 // delete all draggables
820 for (GSList const* i = this->draggables; i != NULL; i = i->next) {
821 delete ((GrDraggable *) i->data);
822 }
823 g_slist_free (this->draggables);
824 this->draggables = NULL;
825 }
827 /**
828 Select the dragger which has the given draggable.
829 */
830 GrDragger *
831 GrDrag::getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke)
832 {
833 for (GList const* i = this->draggers; i != NULL; i = i->next) {
834 GrDragger *dragger = (GrDragger *) i->data;
835 for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
836 GrDraggable *da2 = (GrDraggable *) j->data;
837 if (da2->item == item && da2->point_num == point_num && da2->fill_or_stroke == fill_or_stroke) {
838 return (dragger);
839 }
840 }
841 }
842 return NULL;
843 }
846 void
847 GrDragger::moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
848 {
849 GrDragger *d = this->parent->getDraggerFor (item, point_num, fill_or_stroke);
850 if (d && d != this) {
851 d->moveThisToDraggable (item, point_num, fill_or_stroke, write_repr);
852 }
853 }
856 /**
857 Set selected dragger
858 */
859 void
860 GrDrag::setSelected (GrDragger *dragger)
861 {
862 if (this->selected) {
863 this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
864 g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
865 }
866 if (dragger) {
867 dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
868 g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
869 }
870 this->selected = dragger;
872 this->desktop->emitToolSubselectionChanged((gpointer) dragger);
873 }
875 /**
876 Create a line from p1 to p2 and add it to the lines list
877 */
878 void
879 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
880 {
881 SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(this->desktop),
882 SP_TYPE_CTRLLINE, NULL);
883 sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
884 if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
885 sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
886 sp_canvas_item_show (line);
887 this->lines = g_slist_append (this->lines, line);
888 }
890 /**
891 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
892 new dragger and add it to draggers list
893 */
894 void
895 GrDrag::addDragger (GrDraggable *draggable)
896 {
897 NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_num, draggable->fill_or_stroke);
899 for (GList *i = this->draggers; i != NULL; i = i->next) {
900 GrDragger *dragger = (GrDragger *) i->data;
901 if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
902 // distance is small, merge this draggable into dragger, no need to create new dragger
903 dragger->addDraggable (draggable);
904 dragger->updateKnotShape();
905 return;
906 }
907 }
909 GrDragger *new_dragger = new GrDragger(this, p, draggable);
910 this->draggers = g_list_prepend (this->draggers, new_dragger);
911 }
913 /**
914 Add draggers for the radial gradient rg on item
915 */
916 void
917 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
918 {
919 addDragger (new GrDraggable (item, POINT_RG_CENTER, fill_or_stroke));
920 addDragger (new GrDraggable (item, POINT_RG_FOCUS, fill_or_stroke));
921 addDragger (new GrDraggable (item, POINT_RG_R1, fill_or_stroke));
922 addDragger (new GrDraggable (item, POINT_RG_R2, fill_or_stroke));
923 }
925 /**
926 Add draggers for the linear gradient lg on item
927 */
928 void
929 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
930 {
931 addDragger (new GrDraggable (item, POINT_LG_P1, fill_or_stroke));
932 addDragger (new GrDraggable (item, POINT_LG_P2, fill_or_stroke));
933 }
935 /**
936 Artificially grab the knot of the dragger with this draggable; used by the gradient context
937 */
938 void
939 GrDrag::grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime)
940 {
941 GrDragger *dragger = getDraggerFor (item, point_num, fill_or_stroke);
942 if (dragger) {
943 sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
944 }
945 }
947 /**
948 Regenerates the draggers list from the current selection; is called when selection is changed or
949 modified, also when a radial dragger needs to update positions of other draggers in the gradient
950 */
951 void
952 GrDrag::updateDraggers ()
953 {
954 // delete old draggers and deselect
955 for (GList const* i = this->draggers; i != NULL; i = i->next) {
956 delete ((GrDragger *) i->data);
957 }
958 g_list_free (this->draggers);
959 this->draggers = NULL;
960 this->selected = NULL;
962 g_return_if_fail (this->selection != NULL);
964 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
966 SPItem *item = SP_ITEM(i->data);
967 SPStyle *style = SP_OBJECT_STYLE (item);
969 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
970 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
971 if (SP_IS_LINEARGRADIENT (server)) {
972 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
973 } else if (SP_IS_RADIALGRADIENT (server)) {
974 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
975 }
976 }
978 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
979 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
980 if (SP_IS_LINEARGRADIENT (server)) {
981 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
982 } else if (SP_IS_RADIALGRADIENT (server)) {
983 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
984 }
985 }
988 }
989 }
991 /**
992 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
993 lines are always in sync with the actual gradient
994 */
995 void
996 GrDrag::updateLines ()
997 {
998 // delete old lines
999 for (GSList const *i = this->lines; i != NULL; i = i->next) {
1000 gtk_object_destroy( GTK_OBJECT (i->data));
1001 }
1002 g_slist_free (this->lines);
1003 this->lines = NULL;
1005 g_return_if_fail (this->selection != NULL);
1007 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1009 SPItem *item = SP_ITEM(i->data);
1011 SPStyle *style = SP_OBJECT_STYLE (item);
1013 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
1014 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1015 if (SP_IS_LINEARGRADIENT (server)) {
1016 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);
1017 } else if (SP_IS_RADIALGRADIENT (server)) {
1018 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, true);
1019 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, true), GR_LINE_COLOR_FILL);
1020 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, true), GR_LINE_COLOR_FILL);
1021 }
1022 }
1024 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1025 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1026 if (SP_IS_LINEARGRADIENT (server)) {
1027 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);
1028 } else if (SP_IS_RADIALGRADIENT (server)) {
1029 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, false);
1030 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, false), GR_LINE_COLOR_STROKE);
1031 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, false), GR_LINE_COLOR_STROKE);
1032 }
1033 }
1034 }
1035 }
1037 /**
1038 Regenerates the levels list from the current selection
1039 */
1040 void
1041 GrDrag::updateLevels ()
1042 {
1043 hor_levels.clear();
1044 vert_levels.clear();
1046 g_return_if_fail (this->selection != NULL);
1048 for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1049 SPItem *item = SP_ITEM(i->data);
1050 NR::Rect rect = sp_item_bbox_desktop (item);
1051 // Remember the edges of the bbox and the center axis
1052 hor_levels.push_back(rect.min()[NR::Y]);
1053 hor_levels.push_back(rect.max()[NR::Y]);
1054 hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1055 vert_levels.push_back(rect.min()[NR::X]);
1056 vert_levels.push_back(rect.max()[NR::X]);
1057 vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1058 }
1059 }
1061 void
1062 GrDrag::selected_reverse_vector ()
1063 {
1064 if (selected == NULL)
1065 return;
1067 for (GSList const* i = selected->draggables; i != NULL; i = i->next) {
1068 GrDraggable *draggable = (GrDraggable *) i->data;
1070 sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1071 }
1072 }
1074 void
1075 GrDrag::selected_move (double x, double y)
1076 {
1077 if (selected == NULL)
1078 return;
1080 selected->point += NR::Point (x, y);
1081 selected->point_original = selected->point;
1082 sp_knot_moveto (selected->knot, &(selected->point));
1084 selected->fireDraggables (true);
1086 selected->updateDependencies(true);
1088 // we did an undoable action
1089 sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
1090 _("Move gradient handle"));
1091 }
1093 void
1094 GrDrag::selected_move_screen (double x, double y)
1095 {
1096 gdouble zoom = desktop->current_zoom();
1097 gdouble zx = x / zoom;
1098 gdouble zy = y / zoom;
1100 selected_move (zx, zy);
1101 }
1103 void
1104 GrDrag::select_next ()
1105 {
1106 if (selected == NULL || g_list_find(draggers, selected)->next == NULL) {
1107 if (draggers)
1108 setSelected ((GrDragger *) draggers->data);
1109 } else {
1110 setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data);
1111 }
1112 }
1114 void
1115 GrDrag::select_prev ()
1116 {
1117 if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) {
1118 if (draggers)
1119 setSelected ((GrDragger *) g_list_last (draggers)->data);
1120 } else {
1121 setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data);
1122 }
1123 }
1126 /*
1127 Local Variables:
1128 mode:c++
1129 c-file-style:"stroustrup"
1130 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1131 indent-tabs-mode:nil
1132 fill-column:99
1133 End:
1134 */
1135 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :