Code

:) Wrong line Josh :D
[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 #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     }
157 bool
158 gr_drag_style_set (const SPCSSAttr *css, gpointer data)
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;
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     }
274 GrDrag::~GrDrag()
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;
306 GrDraggable::GrDraggable (SPItem *item, guint point_num, bool fill_or_stroke)
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));
315 GrDraggable::~GrDraggable ()
317     g_object_unref (G_OBJECT (this->item));
320 NR::Point *
321 get_snap_vector (NR::Point p, NR::Point o, double snap, double initial)
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);
333 static void
334 gr_knot_moved_handler(SPKnot *knot, NR::Point const *ppointer, guint state, gpointer data)
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 = &center;
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         }
471         // Move by the smallest of snap vectors:
472         NR::Point move(9999, 9999);
473         for (GSList const *i = snap_vectors; i != NULL; i = i->next) {
474             NR::Point *snap_vector = (NR::Point *) i->data;
475             if (NR::L2(*snap_vector) < NR::L2(move))
476                 move = *snap_vector;
477         }
478         if (move[NR::X] < 9999) {
479             p += move;
480             sp_knot_moveto (knot, &p);
481         }
483         g_slist_free(snap_vectors);
484     }
486     dragger->point = p;
488     if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
489         dragger->fireDraggables (false, true);
490     } else {
491         dragger->fireDraggables (false);
492     }
494     dragger->updateDependencies(false);
497 static void
498 gr_knot_grabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
500     GrDragger *dragger = (GrDragger *) data;
502     sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
505 /**
506 Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
507 */
508 static void
509 gr_knot_ungrabbed_handler (SPKnot *knot, unsigned int state, gpointer data)
511     GrDragger *dragger = (GrDragger *) data;
513     sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
515     dragger->point_original = dragger->point = knot->pos;
517     if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK)) {
518         dragger->fireDraggables (true, true);
519     } else {
520         dragger->fireDraggables (true);
521     }
523     // make this dragger selected
524     dragger->parent->setSelected (dragger);
526     dragger->updateDependencies(true);
528     // we did an undoable action
529     sp_document_done (sp_desktop_document (dragger->parent->desktop), SP_VERB_CONTEXT_GRADIENT, 
530                       _("Move gradient handle"));
533 /**
534 Called when a dragger knot is clicked; selects the dragger
535 */
536 static void
537 gr_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
539    GrDragger *dragger = (GrDragger *) data;
541    dragger->point_original = dragger->point;
543    dragger->parent->setSelected (dragger);
546 /**
547 Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
548 */
549 static void
550 gr_knot_doubleclicked_handler (SPKnot *knot, guint state, gpointer data)
552    GrDragger *dragger = (GrDragger *) data;
554    dragger->point_original = dragger->point;
556    if (dragger->draggables == NULL)
557        return;
559    GrDraggable *draggable = (GrDraggable *) dragger->draggables->data;
560    sp_item_gradient_edit_stop (draggable->item, draggable->point_num, draggable->fill_or_stroke);
563 /**
564 Act upon all draggables of the dragger, setting them to the dragger's point
565 */
566 void
567 GrDragger::fireDraggables (bool write_repr, bool scale_radial, bool merging_focus)
569     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
570         GrDraggable *draggable = (GrDraggable *) i->data;
572         // set local_change flag so that selection_changed callback does not regenerate draggers
573         this->parent->local_change = true;
575         // change gradient, optionally writing to repr; prevent focus from moving if it's snapped
576         // to the center, unless it's the first update upon merge when we must snap it to the point
577         if (merging_focus ||
578             !(draggable->point_num == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->fill_or_stroke)))
579             sp_item_gradient_set_coords (draggable->item, draggable->point_num, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
580     }
583 /**
584 Checks if the dragger has a draggable with this point_num
585  */
586 bool
587 GrDragger::isA (guint point_num)
589     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
590         GrDraggable *draggable = (GrDraggable *) i->data;
591         if (draggable->point_num == point_num) {
592             return true;
593         }
594     }
595     return false;
598 /**
599 Checks if the dragger has a draggable with this item, point_num, fill_or_stroke
600  */
601 bool
602 GrDragger::isA (SPItem *item, guint point_num, bool fill_or_stroke)
604     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
605         GrDraggable *draggable = (GrDraggable *) i->data;
606         if (draggable->point_num == point_num && draggable->item == item && draggable->fill_or_stroke == fill_or_stroke) {
607             return true;
608         }
609     }
610     return false;
613 bool
614 GrDraggable::mayMerge (GrDraggable *da2)
616     if ((this->item == da2->item) && (this->fill_or_stroke == da2->fill_or_stroke)) {
617         // we must not merge the points of the same gradient!
618         if (!((this->point_num == POINT_RG_FOCUS && da2->point_num == POINT_RG_CENTER) ||
619               (this->point_num == POINT_RG_CENTER && da2->point_num == POINT_RG_FOCUS))) {
620             // except that we can snap center and focus together
621             return false;
622         }
623     }
624     return true;
627 bool
628 GrDragger::mayMerge (GrDragger *other)
630     if (this == other)
631         return false;
633     for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
634         GrDraggable *da1 = (GrDraggable *) i->data;
635         for (GSList const* j = other->draggables; j != NULL; j = j->next) { // for all draggables of other
636             GrDraggable *da2 = (GrDraggable *) j->data;
637             if (!da1->mayMerge(da2))
638                 return false;
639         }
640     }
641     return true;
644 bool
645 GrDragger::mayMerge (GrDraggable *da2)
647     for (GSList const* i = this->draggables; i != NULL; i = i->next) { // for all draggables of this
648         GrDraggable *da1 = (GrDraggable *) i->data;
649         if (!da1->mayMerge(da2))
650             return false;
651     }
652     return true;
655 /**
656 Updates the statusbar tip of the dragger knot, based on its draggables
657  */
658 void
659 GrDragger::updateTip ()
661         if (this->knot && this->knot->tip) {
662                 g_free (this->knot->tip);
663                 this->knot->tip = NULL;
664         }
666     if (g_slist_length (this->draggables) == 1) {
667         GrDraggable *draggable = (GrDraggable *) this->draggables->data;
668         char *item_desc = sp_item_description(draggable->item);
669         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"),
670                                            _(gr_knot_descr[draggable->point_num]),
671                                            item_desc,
672                                            draggable->fill_or_stroke == false ? _(" (stroke)") : "");
673         g_free(item_desc);
674     } else if (g_slist_length (draggables) == 2 && isA (POINT_RG_CENTER) && isA (POINT_RG_FOCUS)) {
675         this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
676     } else {
677         int length = g_slist_length (this->draggables);
678         this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
679                                                     "Gradient point shared by <b>%d</b> gradients; drag with <b>Shift</b> to separate",
680                                                     length),
681                                            length);
682     }
685 /**
686 Adds a draggable to the dragger
687  */
688 void
689 GrDragger::updateKnotShape ()
691     if (!draggables)
692         return;
693     GrDraggable *last = (GrDraggable *) g_slist_last(draggables)->data;
694     g_object_set (G_OBJECT (this->knot->item), "shape", gr_knot_shapes[last->point_num], NULL);
697 /**
698 Adds a draggable to the dragger
699  */
700 void
701 GrDragger::addDraggable (GrDraggable *draggable)
703     this->draggables = g_slist_prepend (this->draggables, draggable);
705     this->updateTip();
709 /**
710 Moves this dragger to the point of the given draggable, acting upon all other draggables
711  */
712 void
713 GrDragger::moveThisToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
715     this->point = sp_item_gradient_get_coords (item, point_num, fill_or_stroke);
716     this->point_original = this->point;
718     sp_knot_moveto (this->knot, &(this->point));
720     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
721         GrDraggable *da = (GrDraggable *) i->data;
722         if (da->item == item && da->point_num == point_num && da->fill_or_stroke == fill_or_stroke) {
723             continue;
724         }
725         sp_item_gradient_set_coords (da->item, da->point_num, this->point, da->fill_or_stroke, write_repr, false);
726     }
727     // FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
731 /**
732 Moves all draggables that depend on this one
733  */
734 void
735 GrDragger::updateDependencies (bool write_repr)
737     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
738         GrDraggable *draggable = (GrDraggable *) i->data;
739         switch (draggable->point_num) {
740             case POINT_LG_P1:
741                 // the other point is dependent only when dragging with ctrl+shift
742                 this->moveOtherToDraggable (draggable->item, POINT_LG_P2, draggable->fill_or_stroke, write_repr);
743                 break;
744             case POINT_LG_P2:
745                 this->moveOtherToDraggable (draggable->item, POINT_LG_P1, draggable->fill_or_stroke, write_repr);
746                 break;
747             case POINT_RG_R2:
748                 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
749                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
750                 break;
751             case POINT_RG_R1:
752                 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
753                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
754                 break;
755             case POINT_RG_CENTER:
756                 this->moveOtherToDraggable (draggable->item, POINT_RG_R1, draggable->fill_or_stroke, write_repr);
757                 this->moveOtherToDraggable (draggable->item, POINT_RG_R2, draggable->fill_or_stroke, write_repr);
758                 this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, draggable->fill_or_stroke, write_repr);
759                 break;
760             case POINT_RG_FOCUS:
761                 // nothing can depend on that
762                 break;
763             default:
764                 break;
765         }
766     }
771 GrDragger::GrDragger (GrDrag *parent, NR::Point p, GrDraggable *draggable)
773     this->draggables = NULL;
775     this->parent = parent;
777     this->point = p;
778     this->point_original = p;
780     // create the knot
781     this->knot = sp_knot_new (parent->desktop, NULL);
782     this->knot->setMode(SP_KNOT_MODE_XOR);
783     this->knot->setFill(GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL, GR_KNOT_COLOR_NORMAL);
784     this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
785     sp_knot_update_ctrl(this->knot);
787     // move knot to the given point
788     sp_knot_set_position (this->knot, &p, SP_KNOT_STATE_NORMAL);
789     sp_knot_show (this->knot);
791     // connect knot's signals
792     this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
793     g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
794     g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
795     g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (gr_knot_grabbed_handler), this);
796     g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
798     // add the initial draggable
799     if (draggable)
800         this->addDraggable (draggable);
801     updateKnotShape();
804 GrDragger::~GrDragger ()
806     // unselect if it was selected
807     if (this->parent->selected == this)
808         this->parent->setSelected (NULL);
810     // disconnect signals
811     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_moved_handler), this);
812     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_clicked_handler), this);
813     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_doubleclicked_handler), this);
814     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_grabbed_handler), this);
815     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_ungrabbed_handler), this);
817     /* unref should call destroy */
818     g_object_unref (G_OBJECT (this->knot));
820     // delete all draggables
821     for (GSList const* i = this->draggables; i != NULL; i = i->next) {
822         delete ((GrDraggable *) i->data);
823     }
824     g_slist_free (this->draggables);
825     this->draggables = NULL;
828 /**
829 Select the dragger which has the given draggable.
830 */
831 GrDragger *
832 GrDrag::getDraggerFor (SPItem *item, guint point_num, bool fill_or_stroke)
834     for (GList const* i = this->draggers; i != NULL; i = i->next) {
835         GrDragger *dragger = (GrDragger *) i->data;
836         for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
837             GrDraggable *da2 = (GrDraggable *) j->data;
838             if (da2->item == item && da2->point_num == point_num && da2->fill_or_stroke == fill_or_stroke) {
839                 return (dragger);
840             }
841         }
842     }
843     return NULL;
847 void
848 GrDragger::moveOtherToDraggable (SPItem *item, guint point_num, bool fill_or_stroke, bool write_repr)
850     GrDragger *d = this->parent->getDraggerFor (item, point_num, fill_or_stroke);
851     if (d && d !=  this) {
852         d->moveThisToDraggable (item, point_num, fill_or_stroke, write_repr);
853     }
857 /**
858 Set selected dragger
859 */
860 void
861 GrDrag::setSelected (GrDragger *dragger)
863     if (this->selected) {
864        this->selected->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_NORMAL;
865        g_object_set (G_OBJECT (this->selected->knot->item), "fill_color", GR_KNOT_COLOR_NORMAL, NULL);
866     }
867     if (dragger) {
868         dragger->knot->fill [SP_KNOT_STATE_NORMAL] = GR_KNOT_COLOR_SELECTED;
869         g_object_set (G_OBJECT (dragger->knot->item), "fill_color", GR_KNOT_COLOR_SELECTED, NULL);
870     }
871     this->selected = dragger;
873     this->desktop->emitToolSubselectionChanged((gpointer) dragger);
876 /**
877 Create a line from p1 to p2 and add it to the lines list
878  */
879 void
880 GrDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
882     SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(this->desktop),
883                                                             SP_TYPE_CTRLLINE, NULL);
884     sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
885     if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
886         sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
887     sp_canvas_item_show (line);
888     this->lines = g_slist_append (this->lines, line);
891 /**
892 If there already exists a dragger within MERGE_DIST of p, add the draggable to it; otherwise create
893 new dragger and add it to draggers list
894  */
895 void
896 GrDrag::addDragger (GrDraggable *draggable)
898     NR::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_num, draggable->fill_or_stroke);
900     for (GList *i = this->draggers; i != NULL; i = i->next) {
901         GrDragger *dragger = (GrDragger *) i->data;
902         if (dragger->mayMerge (draggable) && NR::L2 (dragger->point - p) < MERGE_DIST) {
903             // distance is small, merge this draggable into dragger, no need to create new dragger
904             dragger->addDraggable (draggable);
905             dragger->updateKnotShape();
906             return;
907         }
908     }
910     GrDragger *new_dragger = new GrDragger(this, p, draggable);
911     this->draggers = g_list_prepend (this->draggers, new_dragger);
914 /**
915 Add draggers for the radial gradient rg on item
916 */
917 void
918 GrDrag::addDraggersRadial (SPRadialGradient *rg, SPItem *item, bool fill_or_stroke)
920     addDragger (new GrDraggable (item, POINT_RG_CENTER, fill_or_stroke));
921     addDragger (new GrDraggable (item, POINT_RG_FOCUS, fill_or_stroke));
922     addDragger (new GrDraggable (item, POINT_RG_R1, fill_or_stroke));
923     addDragger (new GrDraggable (item, POINT_RG_R2, fill_or_stroke));
926 /**
927 Add draggers for the linear gradient lg on item
928 */
929 void
930 GrDrag::addDraggersLinear (SPLinearGradient *lg, SPItem *item, bool fill_or_stroke)
932     addDragger (new GrDraggable (item, POINT_LG_P1, fill_or_stroke));
933     addDragger (new GrDraggable (item, POINT_LG_P2, fill_or_stroke));
936 /**
937 Artificially grab the knot of the dragger with this draggable; used by the gradient context
938 */
939 void
940 GrDrag::grabKnot (SPItem *item, guint point_num, bool fill_or_stroke, gint x, gint y, guint32 etime)
942     GrDragger *dragger = getDraggerFor (item, point_num, fill_or_stroke);
943     if (dragger) {
944         sp_knot_start_dragging (dragger->knot, dragger->point, x, y, etime);
945     }
948 /**
949 Regenerates the draggers list from the current selection; is called when selection is changed or
950 modified, also when a radial dragger needs to update positions of other draggers in the gradient
951 */
952 void
953 GrDrag::updateDraggers ()
955     // delete old draggers and deselect
956     for (GList const* i = this->draggers; i != NULL; i = i->next) {
957         delete ((GrDragger *) i->data);
958     }
959     g_list_free (this->draggers);
960     this->draggers = NULL;
961     this->selected = NULL;
963     g_return_if_fail (this->selection != NULL);
965     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
967         SPItem *item = SP_ITEM(i->data);
968         SPStyle *style = SP_OBJECT_STYLE (item);
970         if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
971             SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
972             if (SP_IS_LINEARGRADIENT (server)) {
973                 addDraggersLinear (SP_LINEARGRADIENT (server), item, true);
974             } else if (SP_IS_RADIALGRADIENT (server)) {
975                 addDraggersRadial (SP_RADIALGRADIENT (server), item, true);
976             }
977         }
979         if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
980             SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
981             if (SP_IS_LINEARGRADIENT (server)) {
982                 addDraggersLinear (SP_LINEARGRADIENT (server), item, false);
983             } else if (SP_IS_RADIALGRADIENT (server)) {
984                 addDraggersRadial (SP_RADIALGRADIENT (server), item, false);
985             }
986         }
989     }
992 /**
993 Regenerates the lines list from the current selection; is called on each move of a dragger, so that
994 lines are always in sync with the actual gradient
995 */
996 void
997 GrDrag::updateLines ()
999     // delete old lines
1000     for (GSList const *i = this->lines; i != NULL; i = i->next) {
1001         gtk_object_destroy( GTK_OBJECT (i->data));
1002     }
1003     g_slist_free (this->lines);
1004     this->lines = NULL;
1006     g_return_if_fail (this->selection != NULL);
1008     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1010         SPItem *item = SP_ITEM(i->data);
1012         SPStyle *style = SP_OBJECT_STYLE (item);
1014         if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
1015             SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1016             if (SP_IS_LINEARGRADIENT (server)) {
1017                 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);
1018             } else if (SP_IS_RADIALGRADIENT (server)) {
1019                 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, true);
1020                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, true), GR_LINE_COLOR_FILL);
1021                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, true), GR_LINE_COLOR_FILL);
1022             }
1023         }
1025         if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1026             SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1027             if (SP_IS_LINEARGRADIENT (server)) {
1028                 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);
1029             } else if (SP_IS_RADIALGRADIENT (server)) {
1030                 NR::Point center = sp_item_gradient_get_coords (item, POINT_RG_CENTER, false);
1031                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R1, false), GR_LINE_COLOR_STROKE);
1032                 this->addLine (center, sp_item_gradient_get_coords (item, POINT_RG_R2, false), GR_LINE_COLOR_STROKE);
1033             }
1034         }
1035     }
1038 /**
1039 Regenerates the levels list from the current selection
1040 */
1041 void
1042 GrDrag::updateLevels ()
1044     hor_levels.clear();
1045     vert_levels.clear();
1047     g_return_if_fail (this->selection != NULL);
1049     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
1050         SPItem *item = SP_ITEM(i->data);
1051         NR::Rect rect = sp_item_bbox_desktop (item);
1052         // Remember the edges of the bbox and the center axis
1053         hor_levels.push_back(rect.min()[NR::Y]);
1054         hor_levels.push_back(rect.max()[NR::Y]);
1055         hor_levels.push_back(0.5 * (rect.min()[NR::Y] + rect.max()[NR::Y]));
1056         vert_levels.push_back(rect.min()[NR::X]);
1057         vert_levels.push_back(rect.max()[NR::X]);
1058         vert_levels.push_back(0.5 * (rect.min()[NR::X] + rect.max()[NR::X]));
1059     }
1062 void
1063 GrDrag::selected_reverse_vector ()
1065     if (selected == NULL)
1066         return;
1068     for (GSList const* i = selected->draggables; i != NULL; i = i->next) {
1069         GrDraggable *draggable = (GrDraggable *) i->data;
1071         sp_item_gradient_reverse_vector (draggable->item, draggable->fill_or_stroke);
1072     }
1075 void
1076 GrDrag::selected_move (double x, double y)
1078     if (selected == NULL)
1079         return;
1081     selected->point += NR::Point (x, y);
1082     selected->point_original = selected->point;
1083     sp_knot_moveto (selected->knot, &(selected->point));
1085     selected->fireDraggables (true);
1087     selected->updateDependencies(true);
1089     // we did an undoable action
1090     sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
1091                       _("Move gradient handle"));
1094 void
1095 GrDrag::selected_move_screen (double x, double y)
1097     gdouble zoom = desktop->current_zoom();
1098     gdouble zx = x / zoom;
1099     gdouble zy = y / zoom;
1101     selected_move (zx, zy);
1104 void
1105 GrDrag::select_next ()
1107     if (selected == NULL || g_list_find(draggers, selected)->next == NULL) {
1108         if (draggers)
1109             setSelected ((GrDragger *) draggers->data);
1110     } else {
1111         setSelected ((GrDragger *) g_list_find(draggers, selected)->next->data);
1112     }
1115 void
1116 GrDrag::select_prev ()
1118     if (selected == NULL || g_list_find(draggers, selected)->prev == NULL) {
1119         if (draggers)
1120             setSelected ((GrDragger *) g_list_last (draggers)->data);
1121     } else {
1122         setSelected ((GrDragger *) g_list_find(draggers, selected)->prev->data);
1123     }
1127 /*
1128   Local Variables:
1129   mode:c++
1130   c-file-style:"stroustrup"
1131   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1132   indent-tabs-mode:nil
1133   fill-column:99
1134   End:
1135 */
1136 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :