pen-tool.cpp revision 1abe4c9e3e24c2dab97aed7f8a4e64e47b76b548
1N/A#include "shortcuts.h"
1N/A#include "ui/tools/pen-tool.h"
1N/A#include "sp-namedview.h"
1N/A#include "selection.h"
1N/A#include "selection-chemistry.h"
1N/A#include "ui/draw-anchor.h"
1N/A#include "message-stack.h"
1N/A#include "message-context.h"
1N/A#include "preferences.h"
1N/A#include "display/sp-canvas.h"
1N/A#include "pixmaps/cursor-pen.xpm"
1N/A#include "display/canvas-bpath.h"
1N/A#include "display/sp-ctrlline.h"
1N/A#include "display/sodipodi-ctrl.h"
1N/A#include "context-fns.h"
1N/A#include "ui/tools-switch.h"
1N/A#include "ui/control-manager.h"
1N/A#include "live_effects/effect.h"
1N/A#include "live_effects/lpeobject.h"
1N/A#include "live_effects/lpeobject-reference.h"
1N/A#include "live_effects/parameter/path.h"
1N/A#define INKSCAPE_LPE_SPIRO_C
1N/A#include "live_effects/lpe-spiro.h"
1N/A#include "helper/geom-nodetype.h"
1N/A#include "helper/geom-curves.h"
1N/A#include "message-stack.h"
1N/A#include "inkscape.h"
1N/A#include "live_effects/spiro.h"
1N/A#define INKSCAPE_LPE_BSPLINE_C
1N/A#include "live_effects/lpe-bspline.h"
1N/A#include "live_effects/effect.h"
1N/Astatic bool pen_within_tolerance = false;
1N/Astatic int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical
1N/A , polylines_only(false)
1N/A , polylines_paraxial(false)
1N/A , num_clicks(0)
1N/A , events_disabled(false)
1N/A , polylines_only(false)
1N/A , polylines_paraxial(false)
1N/A , num_clicks(0)
1N/A , events_disabled(false)
1N/A if (this->expecting_clicks_for_LPE > 0) {
1N/A // we received too few clicks to sanely set the parameter path so we remove the LPE from the item
1N/A this->_bsplineSpiroColor();
1N/A this->anchor_statusbar = false;
1N/A this->setPolylineMode();
1N/A this->enableSelectionCue();
1N/A this->num_clicks = 0;
1N/A this->_resetColors();
1N/A boost::optional<Geom::Point> origin = this->npoints > 0 ? this->p[0] : boost::optional<Geom::Point>();
1N/A spdc_endpoint_snap_free(this, p, origin, state); // pass the origin, to allow for perpendicular / tangential snapping
1N/A if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
1N/A case GDK_BUTTON_PRESS:
1N/A case GDK_BUTTON_RELEASE:
1N/A case GDK_BUTTON_PRESS:
1N/A case GDK_MOTION_NOTIFY:
1N/A case GDK_BUTTON_RELEASE:
1N/A case GDK_2BUTTON_PRESS:
1N/A case GDK_KEY_PRESS:
1N/A if (this->events_disabled) {
1N/A if(bevent.button != 3 && (this->spiro || this->bspline) && this->npoints > 0 && this->p[0] == this->p[3]){
1N/A // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path)
pen_within_tolerance = true;
switch (this->mode) {
switch (this->state) {
switch (this->state) {
if (this->npoints == 0) {
this->_bsplineSpiroColor();
p = event_dt;
m.unSetup();
ret = true;
if(anchor){
p = event_dt;
this->_setInitialPoint(p);
if (anchor) {
this->green_closed = true;
ret = true;
p = event_dt;
this->_setSubsequentPoint(p, true);
this->state = (this->spiro || this->bspline || this->polylines_only) ? PenTool::POINT : PenTool::CONTROL;
ret = true;
if (this->green_closed) {
this->_finish(true);
this->_finish(false);
ret = true;
this->_finish(false);
ret = true;
if (this->expecting_clicks_for_LPE > 0) {
--this->expecting_clicks_for_LPE;
return ret;
bool ret = false;
if (this->events_disabled) {
if (pen_within_tolerance) {
pen_within_tolerance = false;
switch (this->mode) {
switch (this->state) {
if ( this->npoints != 0 ) {
this->_setSubsequentPoint(p, true);
ret = true;
} else if (!this->sp_event_context_knot_mouseover()) {
m.unSetup();
ret = true;
if (!this->sp_event_context_knot_mouseover()) {
m.unSetup();
switch (this->state) {
if ( this->npoints > 0 ) {
this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path."));
this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path. Shift+Click make a cusp node"));
this->anchor_statusbar = true;
this->anchor_statusbar = false;
ret = true;
this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point."));
this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point. Shift+Click make a cusp node"));
this->anchor_statusbar = true;
this->anchor_statusbar = false;
if (!this->sp_event_context_knot_mouseover()) {
m.unSetup();
if (!this->polylines_only) {
ret = true;
if (!this->sp_event_context_knot_mouseover()) {
m.unSetup();
if(this->bspline){
return ret;
if (this->events_disabled) {
bool ret = false;
if((!anchor || anchor == this->sa) && (this->spiro || this->bspline) && this->npoints > 0 && this->p[0] == this->p[3]){
switch (this->mode) {
switch (this->state) {
if ( this->npoints == 0 ) {
this->_bsplineSpiroColor();
if (anchor) {
if (anchor) {
this->_setInitialPoint(p);
if (anchor) {
if(this->spiro){
this->_finish(true);
switch (this->state) {
if(this->spiro){
if (this->green_closed) {
this->_finish(true);
this->_finish(false);
if (this->grab) {
ret = true;
this->green_closed = false;
this->setPolylineMode();
if (this->waiting_LPE) {
// handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp
return ret;
bool ret = false;
this->_finish(false);
ret = true;
return ret;
if (this->green_bpaths) {
while (this->green_bpaths) {
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvas_shape), this->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
if (this->green_anchor)
if (last_seg) {
if ( cubic &&
this->_bsplineSpiroBuild();
if (this->green_anchor) {
this->_redrawAll();
this->p[1] = this->red_curve->last_segment()->initialPoint() + (1./3.)*(this->red_curve->last_segment()->finalPoint() - this->red_curve->last_segment()->initialPoint());
Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>( this->green_curve->last_segment() );
if ( cubic ){
A = (*cubic)[0];
if (this->spiro) {
C = this->green_curve->last_segment()->finalPoint() + (1./3.)*(this->green_curve->last_segment()->initialPoint() - this->green_curve->last_segment()->finalPoint());
if (this->spiro) {
C = this->green_curve->last_segment()->finalPoint() + (1./3.)*(this->green_curve->last_segment()->initialPoint() - this->green_curve->last_segment()->finalPoint());
this->_bsplineSpiroStartAnchor(false);
this->_redrawAll();
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( this->green_curve->last_segment() );
if ( cubic ) {
this->_bsplineSpiroStartAnchor(true);
this->_redrawAll();
bool ret = false;
gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
if (this->npoints > 0) {
SP_SHORTCUT_SHIFT_MASK : 0 ) |
SP_SHORTCUT_CONTROL_MASK : 0 ) |
SP_SHORTCUT_ALT_MASK : 0 );
if (verb) {
return _undoLastPoint();
case GDK_KEY_KP_Left:
ret = true;
case GDK_KEY_KP_Up:
ret = true;
case GDK_KEY_KP_Right:
ret = true;
case GDK_KEY_KP_Down:
ret = true;
case GDK_KEY_U:
case GDK_KEY_u:
this->_lastpointToCurve();
ret = true;
case GDK_KEY_L:
case GDK_KEY_l:
this->_lastpointToLine();
ret = true;
case GDK_KEY_Return:
case GDK_KEY_KP_Enter:
if (this->npoints != 0) {
this->_finish(false);
ret = true;
case GDK_KEY_Escape:
if (this->npoints != 0) {
this->_cancel ();
ret = true;
case GDK_KEY_g:
case GDK_KEY_G:
ret = true;
case GDK_KEY_BackSpace:
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
return ret;
while (this->green_bpaths) {
if (this->green_anchor) {
this->npoints = 0;
this->red_curve_is_valid = false;
void PenTool::_setAngleDistanceStatusMessage(Geom::Point const p, int pc_point_to_compare, gchar const *message) {
if (angle < 0) {
// this function changes the colors red, green and blue making them transparent or not, depending on if spiro is being used.
if(this->spiro){
}else if(this->bspline){
if((unsigned int)prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) == this->highlight_color){
if((unsigned int)prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) == this->highlight_color){
if (this->green_bpaths) {
while (this->green_bpaths) {
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvas_shape), this->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
this->_bsplineSpiroBuild();
using Geom::X;
using Geom::Y;
Inkscape::LivePathEffect::Effect* thisEffect = SP_LPE_ITEM(this->white_item)->getPathEffectOfType(Inkscape::LivePathEffect::BSPLINE);
if(thisEffect){
if(lpe_bsp){
this->bspline = true;
this->bspline = false;
Inkscape::LivePathEffect::Effect* thisEffect = SP_LPE_ITEM(this->white_item)->getPathEffectOfType(Inkscape::LivePathEffect::SPIRO);
if(thisEffect){
if(lpe_spi){
this->spiro = true;
this->spiro = false;
if(shift){
this->_bsplineSpiroStartAnchorOff();
this->_bsplineSpiroStartAnchorOn();
using Geom::X;
using Geom::Y;
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmp_curve ->last_segment());
if(cubic){
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmp_curve ->last_segment());
if(cubic){
using Geom::X;
using Geom::Y;
if(shift){
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmp_curve ->last_segment());
if(cubic){
if(this->bspline){
if(shift)
if(shift)
if(shift){
this->_bsplineSpiroEndAnchorOff();
this->_bsplineSpiroEndAnchorOn();
this->_bsplineSpiroBuild();
using Geom::X;
using Geom::Y;
bool reverse = false;
reverse = true;
} else if(this->sa){
reverse = true;
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmp_curve ->last_segment());
if(this->bspline){
point_c = tmp_curve ->last_segment()->finalPoint() + (1./3)*(tmp_curve ->last_segment()->initialPoint() - tmp_curve ->last_segment()->finalPoint());
if(cubic){
if (reverse) {
bool reverse = false;
reverse = true;
} else if(this->sa){
reverse = true;
Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmp_curve ->last_segment());
if(cubic){
if (reverse) {
//TODO: CALL TO CLONED FUNCTION SPIRO::doEffect IN lpe-spiro.cpp
if(this->bspline){
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
if(this->spiro){
if (cubic) {
delete in;
if (cubic) {
delete out;
if (cubic) {
delete start;
if (cubic) {
delete end;
delete line_helper;
delete line_helper;
if((cubic && are_near((*cubic)[0],(*cubic)[1])) || (cubic2 && are_near((*cubic2)[2],(*cubic2)[3]))) {
++curve_it1;
++curve_it2;
delete curve_n;
//Spiro function cloned from lpe-spiro.cpp
// commenting the function "doEffect" from src/live_effects/lpe-spiro.cpp
using Geom::X;
using Geom::Y;
int ip = 0;
for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
ip++;
++curve_it1;
++curve_it2;
ip++;
switch (nodetype) {
ip++;
ip++;
ip = 0;
bool is_curve;
// we are drawing horizontal/vertical lines and hit an anchor;
if ((std::abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (std::abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) {
is_curve = false;
is_curve = true;
is_curve = false;
if (statusbar) {
_("<b>Curve segment</b>: angle %3.2f°, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path" ):
_("<b>Line segment</b>: angle %3.2f°, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path");
_("<b>Curve segment</b>: angle %3.2f°, distance %s; with <b>Shift+Click</b> make a cusp node, <b>Enter</b> to finish the path" ):
_("<b>Line segment</b>: angle %3.2f°, distance %s; with <b>Shift+Click</b> make a cusp node, <b>Enter</b> to finish the path");
this->_setAngleDistanceStatusMessage(p, 0, _("<b>Curve handle</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle"));
bool is_symm = false;
is_symm = true;
_("<b>Curve handle, symmetric</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only") :
_("<b>Curve handle</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only");
if (this->polylines_paraxial) {
++num_clicks;
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvas_shape), this->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
// Partial fix for https://bugs.launchpad.net/inkscape/+bug/171990
bool ret = false;
this->_cancel ();
ret = true;
if (this->green_bpaths) {
if (this->bspline){
if (this->green_bpaths) {
if (this->spiro){
Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(this->green_curve->last_segment());
if ( cubic ) {
this->_bsplineSpiroBuild();
ret = true;
return ret;
this->num_clicks = 0;
this->_disableEvents();
this->npoints = 0;
if (this->green_anchor) {
this->_enableEvents();
this->events_disabled = true;
this->events_disabled = false;
void PenTool::waitForLPEMouseClicks(Inkscape::LivePathEffect::EffectType effect_type, unsigned int num_clicks, bool use_polylines) {
int PenTool::nextParaxialDirection(Geom::Point const &pt, Geom::Point const &origin, guint state) const {
return pen_last_paraxial_dir;
if (!snap) {
if (next_dir == 0) {
// selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
m.unSetup();