gradient-drag.cpp revision f200d31b8a03050faa7243929bbd5698db488399
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * On-canvas gradient dragging
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Authors:
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * bulia byak <buliabyak@users.sf.net>
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
fefed98e624e5b375661d137181340caa08440e3johanengelen * Copyright (C) 2007 Johan Engelen
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Copyright (C) 2005 Authors
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm * Released under GNU GPL, read the file 'COPYING' for more information
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm// screen pixels between knots when they snap:
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm// absolute distance between gradient points for them to become a single dragger when the drag is created:
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm// knot shapes corresponding to GrPointType enum
7073d105e612f7dc898c292742bee9655d2a51b2johanengelen N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
7073d105e612f7dc898c292742bee9655d2a51b2johanengelengr_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data)
7073d105e612f7dc898c292742bee9655d2a51b2johanengelengr_drag_sel_modified (Inkscape::Selection */*selection*/, guint /*flags*/, gpointer data)
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmWhen a _query_style_signal is received, check that \a property requests fill/stroke/opacity (otherwise
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmskip), and fill the \a style with the averaged color of all draggables of the selected dragger, if
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrmgr_drag_style_query (SPStyle *style, int property, gpointer data)
0903335a0099bd7ee779925f43a15a2216a0e863johanengelen if (property != QUERY_STYLE_PROPERTY_FILL && property != QUERY_STYLE_PROPERTY_STROKE && property != QUERY_STYLE_PROPERTY_MASTEROPACITY) {
d431763a9ec8059aa4962688de8144319969fb0fjohanengelen for (GList *i = drag->selected; i != NULL; i = i->next) { // for all selected draggers
fefed98e624e5b375661d137181340caa08440e3johanengelen for (GSList const* j = d->draggables; j != NULL; j = j->next) { // for all draggables of dragger
fefed98e624e5b375661d137181340caa08440e3johanengelen GrDraggable *draggable = (GrDraggable *) j->data;
d431763a9ec8059aa4962688de8144319969fb0fjohanengelen guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
fefed98e624e5b375661d137181340caa08440e3johanengelen // set both fill and stroke with our stop-color and stop-opacity
d90df4b5134fecb1e7248afbf601bae9d55682c6johanengelen style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (1.0);
d90df4b5134fecb1e7248afbf601bae9d55682c6johanengelen style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (1.0);
f07bfd5a05d43a6d11f7cd442f085149092dea88pjrm return false;
if ((css->attribute("fill") && !css->attribute("stroke") && !strcmp(css->attribute("fill"), "none")) ||
sp_repr_css_set_property (stop, "stop-opacity", "0"); // if a single fill/stroke property is set to none, don't change color, set opacity to 0
for (GList const* sel = drag->selected; sel != NULL; sel = sel->next) { // for all selected draggers
for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
sp_item_gradient_stop_set_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke, stop);
if (!selected) return 0;
int count = 0;
guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
count ++;
if (count) {
SPStop *
bool fill_or_stroke = true;
bool r1_knot = false;
bool addknot = false;
addknot = true;
addknot = true;
r1_knot = true;
addknot = true;
r1_knot = false;
if (addknot) {
if (!next_stop) {
return NULL;
return newstop;
return NULL;
local_change = true;
sp_item_gradient_stop_set_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke, stop);
bool over_line = false;
if (lines) {
if (stop) {
this->local_change = false;
(gpointer)this )
(gpointer)this )
(gpointer)this )
(gpointer)this )
this->updateDraggers ();
this->updateLines ();
this->updateLevels ();
this->setSelected (getDraggerFor (desktop->gr_item, desktop->gr_point_type, desktop->gr_point_i, desktop->gr_fill_or_stroke));
if (this->selected) {
deselect_all();
SPObject *
if (!item)
return NULL;
if (fill_or_stroke)
return server;
double r = L2 (p - o);
for (GSList const* i = dragger->draggables; i != NULL; i = i->next) { // for all draggables of dragger
GrDraggable *da_new = new GrDraggable (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
delete dragger;
Inkscape::SnappedPoint s = m.freeSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
if (s.getSnapped()) {
p = s.getPoint();
} else if (draggable->point_type == POINT_RG_R1 || draggable->point_type == POINT_RG_R2 || draggable->point_type == POINT_RG_FOCUS) {
snap_vector = get_snap_vector (p, dr_snap, M_PI/2, Geom::atan2 (dragger->point_original - dr_snap));
if (snap_vector) {
Inkscape::SnappedPoint s = m.constrainedSnap(Inkscape::SnapCandidatePoint(p + *snap_vector, Inkscape::SNAPSOURCE_OTHER_HANDLE), cl);
if (s.getSnapped()) {
Inkscape::SnappedPoint dummy(p + *snap_vector, Inkscape::SNAPSOURCE_OTHER_HANDLE, 0, Inkscape::SNAPTARGET_CONSTRAINED_ANGLE, Geom::L2(*snap_vector), 10000, true, false);
Inkscape::SnappedPoint bsp = m.findBestSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE), sc, true); // snap indicator will be displayed if needed
gr_midpoint_limits(GrDragger *dragger, SPObject *server, Geom::Point *begin, Geom::Point *end, Geom::Point *low_lim, Geom::Point *high_lim, GSList **moving)
d_add = drag->getDraggerFor(draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
d_add = drag->getDraggerFor(draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, lowest_i - 1, draggable->fill_or_stroke);
if (d_temp)
d_temp = drag->getDraggerFor (draggable->item, POINT_LG_MID, highest_i + 1, draggable->fill_or_stroke);
if (d_temp)
d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, lowest_i - 1, draggable->fill_or_stroke);
if (d_temp)
d_temp = drag->getDraggerFor (draggable->item, draggable->point_type, highest_i + 1, draggable->fill_or_stroke);
d_temp = drag->getDraggerFor (draggable->item, (draggable->point_type==POINT_RG_MID1) ? POINT_RG_R1 : POINT_RG_R2, num-1, draggable->fill_or_stroke);
if (d_temp)
gr_knot_moved_midpoint_handler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state, gpointer data)
if (Geom::L2(drg->point - dragger->point) + Geom::L2(drg->point - begin) - 1e-3 > Geom::L2(dragger->point - begin)) { // drg is on the end side from dragger
Called when the mouse releases a dragger knot; changes gradient writing to repr, updates other draggers if needed
if (d == dragger)
d->fireDraggables (true);
if (!draggable) return;
switch (draggable->point_type) { // if we delete first or last stop, move the next/previous to the edge
case POINT_LG_BEGIN:
case POINT_RG_CENTER:
if (next) {
case POINT_LG_END:
case POINT_RG_R1:
case POINT_RG_R2:
if (prev) {
case POINT_LG_MID:
case POINT_RG_MID1:
case POINT_RG_MID2:
Called when a dragger knot is doubleclicked; opens gradient editor with the stop from the first draggable
sp_item_gradient_edit_stop (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
if (merging_focus ||
!(draggable->point_type == POINT_RG_FOCUS && this->isA(draggable->item, POINT_RG_CENTER, draggable->point_i, draggable->fill_or_stroke)))
sp_item_gradient_set_coords (draggable->item, draggable->point_type, draggable->point_i, this->point, draggable->fill_or_stroke, write_repr, scale_radial);
if ( (draggable->point_type == point_type) && (draggable->point_i == point_i) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
if ( (draggable->point_type == point_type) && (draggable->item == item) && (draggable->fill_or_stroke == fill_or_stroke) ) {
if (this == other)
case POINT_LG_MID:
case POINT_RG_MID1:
case POINT_RG_MID2:
this->knot->tip = g_strdup_printf (_("%s %d for: %s%s; drag with <b>Ctrl</b> to snap offset; click with <b>Ctrl+Alt</b> to delete stop"),
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"),
this->knot->tip = g_strdup_printf (_("Radial gradient <b>center</b> and <b>focus</b>; drag with <b>Shift</b> to separate focus"));
this->knot->tip = g_strdup_printf (ngettext("Gradient point shared by <b>%d</b> gradient; drag with <b>Shift</b> to separate",
length),
length);
if (!draggables)
this->updateTip();
GrDragger::moveThisToDraggable (SPItem *item, gint point_type, gint point_i, bool fill_or_stroke, bool write_repr)
sp_item_gradient_set_coords (da->item, da->point_type, da->point_i, this->point, da->fill_or_stroke, write_repr, false);
// FIXME: here we should also call this->updateDependencies(write_repr); to propagate updating, but how to prevent loops?
if (!server)
this->moveOtherToDraggable (draggable->item, POINT_LG_MID, i, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, i, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, i, draggable->fill_or_stroke, write_repr);
case POINT_LG_BEGIN:
this->moveOtherToDraggable (draggable->item, POINT_LG_END, -1, draggable->fill_or_stroke, write_repr);
case POINT_LG_END:
this->moveOtherToDraggable (draggable->item, POINT_LG_BEGIN, 0, draggable->fill_or_stroke, write_repr);
case POINT_LG_MID:
case POINT_RG_R2:
this->moveOtherToDraggable (draggable->item, POINT_RG_R1, -1, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
case POINT_RG_R1:
this->moveOtherToDraggable (draggable->item, POINT_RG_R2, -1, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
case POINT_RG_CENTER:
this->moveOtherToDraggable (draggable->item, POINT_RG_R1, -1, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_R2, -1, draggable->fill_or_stroke, write_repr);
this->moveOtherToDraggable (draggable->item, POINT_RG_FOCUS, -1, draggable->fill_or_stroke, write_repr);
case POINT_RG_FOCUS:
case POINT_RG_MID1:
this->moveOtherToDraggable (draggable->item, POINT_RG_MID2, draggable->point_i, draggable->fill_or_stroke, write_repr);
case POINT_RG_MID2:
this->moveOtherToDraggable (draggable->item, POINT_RG_MID1, draggable->point_i, draggable->fill_or_stroke, write_repr);
: point(p),
this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_midpoint_handler), this);
this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
if (draggable)
g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_moved_handler), this);
g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_clicked_handler), this);
g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_doubleclicked_handler), this);
g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_grabbed_handler), this);
g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (gr_knot_ungrabbed_handler), this);
return (dragger);
return NULL;
GrDragger::moveOtherToDraggable (SPItem *item, gint point_type, gint point_i, bool fill_or_stroke, bool write_repr)
while (selected) {
deselect_all();
setSelected (d, true, true);
setSelected (d, true, true);
setSelected (d, true, true);
if (add_to_selection) {
if (!dragger) return;
if (override) {
if (selected) {
deselect_all();
if (dragger) {
if (seldragger) {
if (rgba != GR_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
Geom::Point p = sp_item_gradient_get_coords (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke);
// fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
if (dragger) {
GrDrag::grabKnot (SPItem *item, gint point_type, gint point_i, bool fill_or_stroke, gint x, gint y, guint32 etime)
if (dragger) {
while (selected) {
this->addLine (item, sp_item_gradient_get_coords (item, POINT_LG_BEGIN, 0, true), sp_item_gradient_get_coords (item, POINT_LG_END, 0, true), GR_LINE_COLOR_FILL);
this->addLine (item, center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, true), GR_LINE_COLOR_FILL);
this->addLine (item, center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, true), GR_LINE_COLOR_FILL);
this->addLine (item, sp_item_gradient_get_coords (item, POINT_LG_BEGIN, 0, false), sp_item_gradient_get_coords (item, POINT_LG_END, 0, false), GR_LINE_COLOR_STROKE);
this->addLine (item, center, sp_item_gradient_get_coords (item, POINT_RG_R1, 0, false), GR_LINE_COLOR_STROKE);
this->addLine (item, center, sp_item_gradient_get_coords (item, POINT_RG_R2, 0, false), GR_LINE_COLOR_STROKE);
if (rect) {
bool did = false;
bool skip_radius_with_center = false;
skip_radius_with_center = true;
did = true;
did = true;
if (draggers)
setSelected (d);
if (draggers)
setSelected (d);
if (!selected) return;
struct StructStopInfo {
while (selected) {
case POINT_LG_MID:
case POINT_RG_MID1:
case POINT_RG_MID2:
bool present = false;
present = true;
if (!present)
case POINT_LG_BEGIN:
case POINT_LG_END:
case POINT_RG_CENTER:
case POINT_RG_R1:
case POINT_RG_R2:
if (stop) {
bool present = false;
present = true;
if (!present)
while (midstoplist) {
while (endstoplist) {
// cannot use vector->vector.stops.size() because the vector might be invalidated by deletion of a midstop
int len = 0;
case POINT_LG_BEGIN:
case POINT_LG_END:
case POINT_RG_CENTER:
if (newfirst) {
case POINT_RG_R1:
case POINT_RG_R2:
sp_repr_css_set_property(css, "fill", sp_repr_css_property(stopcss, "stop-color", "inkscape:unset"));
sp_repr_css_set_property(css, "stroke", sp_repr_css_property(stopcss, "stop-color", "inkscape:unset"));
sp_repr_css_set_property(css, "stroke-opacity", sp_repr_css_property(stopcss, "stop-opacity", "1"));
delete stopinfo;
if (document) {