Code

7e23b5750ec2e98e995733fc9ee3727ac9e7fd9e
[inkscape.git] / src / gradient-drag.cpp
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     }
153 bool
154 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
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;
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     }
270 GrDrag::~GrDrag()
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;
302 GrDraggable::GrDraggable (SPItem *item, guint point_num, bool fill_or_stroke)
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));
311 GrDraggable::~GrDraggable ()
313     g_object_unref (G_OBJECT (this->item));
316 NR::Point *
317 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
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);
329 static void
330 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
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 = &center;
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);
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)
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));
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)
521    GrDragger *dragger = (GrDragger *) data;
523    dragger->point_original = dragger->point;
525    dragger->parent->setSelected (dragger);
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)
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);
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)
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     }
565 /**
566 Checks if the dragger has a draggable with this point_num
567  */
568 bool
569 GrDragger::isA (guint point_num)
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;
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)
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;
595 bool
596 GrDraggable::mayMerge (GrDraggable *da2)
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;
609 bool
610 GrDragger::mayMerge (GrDragger *other)
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;
626 bool
627 GrDragger::mayMerge (GrDraggable *da2)
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;
637 /**
638 Updates the statusbar tip of the dragger knot, based on its draggables
639  */
640 void
641 GrDragger::updateTip ()
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         this->knot->tip = g_strdup_printf (_("Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate"),
660                                            g_slist_length (this->draggables));
661     }
664 /**
665 Adds a draggable to the dragger
666  */
667 void
668 GrDragger::updateKnotShape ()
670     if (!draggables)
671         return;
672     GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
673     g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_num], NULL);
676 /**
677 Adds a draggable to the dragger
678  */
679 void
680 GrDragger::addDraggable (GrDraggable *draggable)
682     this->draggables = g_slist_prepend (this->draggables, draggable);
684     this->updateTip();
688 /**
689 Moves this dragger to the point of the given draggable, acting upon all other draggables
690  */
691 void
692 GrDragger::moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
694     this->point = sp_item_gradient_get_coords (item, point_num, fill_or_stroke);
695     this->point_original = this->point;
697     sp_knot_moveto (this->knot, &(this->point));
699     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
700         GrDraggable *da = (GrDraggable *) i->data;
701         if (da->item == item && da->point_num == point_num && da->fill_or_stroke == fill_or_stroke) {
702             continue;
703         }
704         sp_item_gradient_set_coords (da->item, da->point_num, this->point, da->fill_or_stroke, write_repr, false);
705     }
706     // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
710 /**
711 Moves all draggables that depend on this one
712  */
713 void
714 GrDragger::updateDependencies (bool write_repr)
716     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
717         GrDraggable *draggable = (GrDraggable *) i->data;
718         switch (draggable->point_num) {
719             case POINT_LG_P1:
720                 // the other point is dependent only when dragging with ctrl+shift
721                 this->moveOtherToDraggable (draggable->item, POINT_LG_P2, draggable->fill_or_stroke, write_repr);
722                 break;
723             case POINT_LG_P2:
724                 this->moveOtherToDraggable (draggable->item, POINT_LG_P1, draggable->fill_or_stroke, write_repr);
725                 break;
726             case POINT_RG_R2:
727                 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
728                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
729                 break;
730             case POINT_RG_R1:
731                 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
732                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
733                 break;
734             case POINT_RG_CENTER:
735                 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
736                 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
737                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
738                 break;
739             case POINT_RG_FOCUS:
740                 // nothing can depend on that
741                 break;
742             default:
743                 break;
744         }
745     }
750 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
752     this->draggables = NULL;
754     this->parent = parent;
756     this->point = p;
757     this->point_original = p;
759     // create the knot
760     this->knot = sp_knot_new (parent->desktop, NULL);
761     g_object_set (G_OBJECT (this->knot->item), "mode", SP_KNOT_MODE_XOR, NULL);
762     this->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
763     this->knot->stroke [SP_KNOT_STATE_NORMAL] = 0x000000ff;
764     this->knot->stroke [SP_KNOT_STATE_DRAGGING] = 0x000000ff;
765     this->knot->stroke [SP_KNOT_STATE_MOUSEOVER] = 0x000000ff;
766     g_object_set (G_OBJECT (this->knot->item), "stroke_color", 0x000000ff, NULL);
768     // move knot to the given point
769     sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
770     sp_knot_show (this->knot);
772     // connect knot's signals
773     this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
774     g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
775     g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
776     g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
778     // add the initial draggable
779     if (draggable)
780         this->addDraggable (draggable);
781     updateKnotShape();
784 GrDragger::~GrDragger ()
786     // unselect if it was selected
787     if (this->parent->selected == this)
788         this->parent->setSelected (NULL);
790     /* unref should call destroy */
791     g_object_unref (G_OBJECT (this->knot));
793     // delete all draggables
794     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
795         delete ((GrDraggable *) i->data);
796     }
797     g_slist_free (this->draggables);
798     this->draggables = NULL;
801 /**
802 Select the dragger which has the given draggable.
803 */
804 GrDragger *
805 GrDrag::getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke)
807     for (GList const* i = this->draggers; i != NULL; i = i->next) {
808         GrDragger *dragger = (GrDragger *) i->data;
809         for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
810             GrDraggable *da2 = (GrDraggable *) j->data;
811             if (da2->item == item && da2->point_num == point_num && da2->fill_or_stroke == fill_or_stroke) {
812                 return (dragger);
813             }
814         }
815     }
816     return NULL;
820 void
821 GrDragger::moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
823     GrDragger *d = this->parent->getDraggerFor (item, point_num, fill_or_stroke);
824     if (d && d !=  this) {
825         d->moveThisToDraggable (item, point_num, fill_or_stroke, write_repr);
826     }
830 /**
831 Set selected dragger
832 */
833 void
834 GrDrag::setSelected (GrDragger *dragger)
836     if (this->selected) {
837        this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
838        g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
839     }
840     if (dragger) {
841         dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
842         g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
843     }
844     this->selected = dragger;
846     this->desktop->emitToolSubselectionChanged((gpointer) dragger);
849 /**
850 Create a line from p1 to p2 and add it to the lines list
851  */
852 void
853 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
855     SPCanvasItem *line = sp_canvas_item_new(SP_DT_CONTROLS(this->desktop),
856                                                             SP_TYPE_CTRLLINE, NULL);
857     sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
858     if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
859         sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
860     sp_canvas_item_show (line);
861     this->lines = g_slist_append (this->lines, line);
864 /**
865 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
866 new dragger and add it to draggers list
867  */
868 void
869 GrDrag::addDragger (GrDraggable *draggable)
871     NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_num, draggable->fill_or_stroke);
873     for (GList *i = this->draggers; i != NULL; i = i->next) {
874         GrDragger *dragger = (GrDragger *) i->data;
875         if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
876             // distance is small, merge this draggable into dragger, no need to create new dragger
877             dragger->addDraggable (draggable);
878             dragger->updateKnotShape();
879             return;
880         }
881     }
883     GrDragger *new_dragger = new GrDragger(this, p, draggable);
884     this->draggers = g_list_prepend (this->draggers, new_dragger);
887 /**
888 Add draggers for the radial gradient rg on item
889 */
890 void
891 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
893     addDragger (new GrDraggable (item, POINT_RG_CENTER, fill_or_stroke));
894     addDragger (new GrDraggable (item, POINT_RG_FOCUS, fill_or_stroke));
895     addDragger (new GrDraggable (item, POINT_RG_R1, fill_or_stroke));
896     addDragger (new GrDraggable (item, POINT_RG_R2, fill_or_stroke));
899 /**
900 Add draggers for the linear gradient lg on item
901 */
902 void
903 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
905     addDragger (new GrDraggable (item, POINT_LG_P1, fill_or_stroke));
906     addDragger (new GrDraggable (item, POINT_LG_P2, fill_or_stroke));
909 /**
910 Artificially grab the knot of the dragger with this draggable; used by the gradient context
911 */
912 void
913 GrDrag::grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime)
915     GrDragger *dragger = getDraggerFor (item, point_num, fill_or_stroke);
916     if (dragger) {
917         sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
918     }
921 /**
922 Regenerates the draggers list from the current selection; is called when selection is changed or
923 modified, also when a radial dragger needs to update positions of other draggers in the gradient
924 */
925 void
926 GrDrag::updateDraggers ()
928     // delete old draggers and deselect
929     for (GList const* i = this->draggers; i != NULL; i = i->next) {
930         delete ((GrDragger *) i->data);
931     }
932     g_list_free (this->draggers);
933     this->draggers = NULL;
934     this->selected = NULL;
936     g_return_if_fail (this->selection != NULL);
938     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
940         SPItem *item = SP_ITEM(i->data);
941         SPStyle *style = SP_OBJECT_STYLE (item);
943         if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
944             SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
945             if (SP_IS_LINEARGRADIENT (server)) {
946                 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
947             } else if (SP_IS_RADIALGRADIENT (server)) {
948                 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
949             }
950         }
952         if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
953             SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
954             if (SP_IS_LINEARGRADIENT (server)) {
955                 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
956             } else if (SP_IS_RADIALGRADIENT (server)) {
957                 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
958             }
959         }
962     }
965 /**
966 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
967 lines are always in sync with the actual gradient
968 */
969 void
970 GrDrag::updateLines ()
972     // delete old lines
973     for (GSList const *i = this->lines; i != NULL; i = i->next) {
974         gtk_object_destroy( GTK_OBJECT (i->data));
975     }
976     g_slist_free (this->lines);
977     this->lines = NULL;
979     g_return_if_fail (this->selection != NULL);
981     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
983         SPItem *item = SP_ITEM(i->data);
985         SPStyle *style = SP_OBJECT_STYLE (item);
987         if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
988             SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
989             if (SP_IS_LINEARGRADIENT (server)) {
990                 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);
991             } else if (SP_IS_RADIALGRADIENT (server)) {
992                 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, true);
993                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, true), GR_LINE_COLOR_FILL);
994                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, true), GR_LINE_COLOR_FILL);
995             }
996         }
998         if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
999             SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1000             if (SP_IS_LINEARGRADIENT (server)) {
1001                 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);
1002             } else if (SP_IS_RADIALGRADIENT (server)) {
1003                 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, false);
1004                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, false), GR_LINE_COLOR_STROKE);
1005                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, false), GR_LINE_COLOR_STROKE);
1006             }
1007         }
1008     }
1011 /**
1012 Regenerates the levels list from the current selection
1013 */
1014 void
1015 GrDrag::updateLevels ()
1017     hor_levels.clear();
1018     vert_levels.clear();
1020     g_return_if_fail (this->selection != NULL);
1022     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1023         SPItem *item = SP_ITEM(i->data);
1024         NR::Rect rect = sp_item_bbox_desktop (item);
1025         // Remember the edges of the bbox and the center axis
1026         hor_levels.push_back(rect.min()[NR::Y]);
1027         hor_levels.push_back(rect.max()[NR::Y]);
1028         hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1029         vert_levels.push_back(rect.min()[NR::X]);
1030         vert_levels.push_back(rect.max()[NR::X]);
1031         vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1032     }
1035 void
1036 GrDrag::selected_reverse_vector ()
1038     if (selected == NULL)
1039         return;
1041     for (GSList const* i = selected->draggables; i != NULL; i = i->next) {
1042         GrDraggable *draggable = (GrDraggable *) i->data;
1044         sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1045     }
1048 void
1049 GrDrag::selected_move (double x, double y)
1051     if (selected == NULL)
1052         return;
1054     selected->point += NR::Point (x, y);
1055     selected->point_original = selected->point;
1056     sp_knot_moveto (selected->knot, &(selected->point));
1058     selected->fireDraggables (true);
1060     selected->updateDependencies(true);
1062     // we did an undoable action
1063     sp_document_done (SP_DT_DOCUMENT (desktop));
1066 void
1067 GrDrag::selected_move_screen (double x, double y)
1069     gdouble zoom = desktop->current_zoom();
1070     gdouble zx = x / zoom;
1071     gdouble zy = y / zoom;
1073     selected_move (zx, zy);
1076 void
1077 GrDrag::select_next ()
1079     if (selected == NULL || g_list_find(draggers, selected)->next == NULL) {
1080         if (draggers)
1081             setSelected ((GrDragger *) draggers->data);
1082     } else {
1083         setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data);
1084     }
1087 void
1088 GrDrag::select_prev ()
1090     if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) {
1091         if (draggers)
1092             setSelected ((GrDragger *) g_list_last (draggers)->data);
1093     } else {
1094         setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data);
1095     }
1099 /*
1100   Local Variables:
1101   mode:c++
1102   c-file-style:"stroustrup"
1103   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1104   indent-tabs-mode:nil
1105   fill-column:99
1106   End:
1107 */
1108 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :