node.cpp revision 4aeaa765c1779bb282e9cb7b8ad14cc1fae248ef
/* Authors:
* Krzysztof Kosiński <tweenk.pl@gmail.com>
* Jon A. Cruz <jon@joncruz.org>
*
* Copyright (C) 2009 Authors
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include <iostream>
#include <stdexcept>
#include <boost/utility.hpp>
#include "multi-path-manipulator.h"
#include "display/sp-ctrlline.h"
#include "display/sp-canvas.h"
#include "display/sp-canvas-util.h"
#include "desktop.h"
#include "desktop-handles.h"
#include "preferences.h"
#include "snap.h"
#include "snap-preferences.h"
#include "sp-namedview.h"
#include "ui/control-manager.h"
#include "ui/tool/control-point-selection.h"
#include "ui/tool/event-utils.h"
#include "ui/tool/path-manipulator.h"
#include <gdk/gdkkeysyms.h>
#include <math.h>
namespace {
{
switch(type) {
break;
break;
break;
default:
break;
}
return result;
}
} // namespace
namespace Inkscape {
namespace UI {
{0xbfbfbf00, 0x000000ff}, // normal fill, stroke
{0xff000000, 0x000000ff}, // mouseover fill, stroke
{0xff000000, 0x000000ff}, // clicked fill, stroke
//
{0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
{0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
{0xff000000, 0x000000ff} // clicked fill, stroke when selected
};
{0xffffffff, 0x000000ff}, // normal fill, stroke
{0xff000000, 0x000000ff}, // mouseover fill, stroke
{0xff000000, 0x000000ff}, // clicked fill, stroke
//
{0xffffffff, 0x000000ff}, // normal fill, stroke
{0xff000000, 0x000000ff}, // mouseover fill, stroke
{0xff000000, 0x000000ff} // clicked fill, stroke
};
{
switch(type) {
default: out << 'b'; break;
}
return out;
}
/** Computes an unit vector of the direction from first to second control point */
}
_degenerate(true)
{
setVisible(false);
}
{
//sp_canvas_item_hide(_handle_line);
}
void Handle::setVisible(bool v)
{
ControlPoint::setVisible(v);
if (v) {
} else {
}
}
{
// The handle becomes degenerate.
// Adjust node type as necessary.
if (other->isDegenerate()) {
// If both handles become degenerate, convert to parent cusp node
} else {
// Only 1 handle becomes degenerate
case NODE_AUTO:
case NODE_SYMMETRIC:
break;
default:
// do nothing for other node types
break;
}
}
// If the segment between the handle and the node
// in its direction becomes linear and there are smooth nodes
// at its ends, make their handles colinear with the segment
}
}
}
//move the handler and its oposite the same proportion
}
return;
}
// restrict movement to the line joining the nodes
// project the relative position on the direction line
//move the handler and its oposite the same proportion
}
return;
}
case NODE_AUTO:
// fall through - auto nodes degrade into smooth nodes
case NODE_SMOOTH: {
/* for smooth nodes, we need to rotate the other handle so that it's colinear
* with the dragged one while conserving length. */
} break;
case NODE_SYMMETRIC:
// for symmetric nodes, place the other handle on the opposite side
break;
default: break;
}
// moves the handler and its oposite the same proportion
}
}
{
ControlPoint::setPosition(p);
// update degeneration info and visibility
_degenerate = true;
else _degenerate = false;
setVisible(true);
} else {
setVisible(false);
}
}
{
if (isDegenerate()) return;
}
{
}
{
}
{
}
{
switch(type) {
case NODE_CUSP: return _("Cusp node handle");
case NODE_SMOOTH: return _("Smooth node handle");
case NODE_SYMMETRIC: return _("Symmetric node handle");
case NODE_AUTO: return _("Auto-smooth node handle");
default: return "";
}
}
{
{
case GDK_KEY_PRESS:
{
case GDK_KEY_s:
case GDK_KEY_S:
// when Shift+S is pressed when hovering over a handle belonging to a cusp node,
// hold this handle in place; otherwise process normally
// this handle is guaranteed not to be degenerate
return true;
}
break;
default: break;
}
// new double click event to set the handlers of a node to the default proportion, 0.3334%
case GDK_2BUTTON_PRESS:
break;
default: break;
}
}
//this function moves the handler and its oposite to the default proportion of 0.3334
void Handle::handle_2button_press(){
}
}
{
_pm()._handleGrabbed();
return false;
}
{
// with Alt, preserve length
snap = false;
}
// with Ctrl, constrain to M_PI/rotationsnapsperpi increments from vertical
// and the original position.
if (held_control(*event)) {
// note: if snapping to the original position is only desired in the original
// direction of the handle, change to Ray instead of Line
}
}
// moves the handler and its oposite in X fixed positions depending on parameter "steps with control"
// by default in live BSpline
}
}
//if the snap adjustment is activated and it is not bspline
}
Inkscape::SnappedPoint p;
} else if (ctrl_constraint) {
// NOTE: this is subtly wrong.
// We should get all possible constraints and snap along them using
// multipleConstrainedSnaps, instead of first snapping to angle and then to objects
Inkscape::SnappedPoint p;
p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), *ctrl_constraint);
} else {
}
}
// with Shift, if the node is cusp, rotate the other handle as well
if (held_shift(*event)) {
} else {
// restore the position
}
}
//if it is bspline but SHIFT or CONTROL are not pressed it fixes it in the original position
}
}
{
// hide the handle if it's less than dragtolerance away from the node
// however, never do this for cancelled drag / broken grab
// TODO is this actually a good idea?
if (event) {
//sets the bspline strength to 0.0000
}
}
}
// HACK: If the handle was dragged out, call parent's ungrabbed handler,
// so that transform handles reappear
if (_drag_out) {
}
_drag_out = false;
_pm()._handleUngrabbed();
}
{
return true;
}
{
}
{
} else {
}
}
static double snap_increment_degrees() {
return 180.0 / snaps;
}
{
char const *more;
// a trick to mark as bspline if the node has no strength, we are going to use it later
// to show the appropiate messages. We cannot do it in any different way becasue the function is constant
bool isBSpline = false;
isBSpline = true;
if (can_shift_rotate && !isBSpline) {
} else if(isBSpline){
}else {
}
if (state_held_control(state)) {
"<b>Shift+Ctrl+Alt</b>: preserve length and snap rotation angle to %g° "
"increments while rotating both handles"),
} else {
"<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %g° increments"),
}
} else {
return C_("Path handle tip",
"<b>Shift+Alt</b>: preserve handle length and rotate both handles");
} else {
return C_("Path handle tip",
"<b>Alt</b>: preserve handle length while dragging");
}
}
} else {
if (state_held_control(state)) {
"<b>Shift+Ctrl</b>: snap rotation angle to %g° increments and rotate both handles"),
} else if(isBSpline){
"<b>Ctrl</b>: Move handle by his actual steps in BSpline Live Effect"));
}else{
"<b>Ctrl</b>: snap rotation angle to %g° increments, click to retract"),
}
return C_("Path hande tip",
"<b>Shift</b>: rotate both handles by the same angle");
return C_("Path hande tip",
"<b>Shift</b>: move handle");
}
}
case NODE_AUTO:
"<b>Auto node handle</b>: drag to convert to smooth node (%s)"), more);
default:
if(!isBSpline){
"<b>Auto node handle</b>: drag to convert to smooth node (%s)"), more);
}else{
"<b>BSpline node handle</b>: Shift to drag, double click to reset (%s)"), more);
}
}
}
{
// report angle in mathematical convention
g_string_free(x, TRUE);
g_string_free(y, TRUE);
return ret;
}
_handles_shown(false)
{
// NOTE we do not set type here, because the handles are still degenerate
}
{
}
// NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined.
{
if (n) {
return n.ptr();
} else {
return NULL;
}
}
{
}
{
if (p) {
return p.ptr();
} else {
return NULL;
}
}
{
// move handles when the node moves.
// save the previous node strength to apply it again once the node is moved
double oldPos = 0.0000;
Node *n = this;
oldPos = n->bsplineWeight;
}
// if the node has a smooth handle after a line segment, it should be kept colinear
// with the segment
// move the affected handlers. First the node ones, later the adjoining ones.
_pm().BSplineNodeHandlesReposition(this);
}
}
{
// save the previous node strength to apply it again later when the node is moved
double oldPos = 0.0000;
oldPos = this->bsplineWeight;
}
setPosition(position() * m);
/* Affine transforms keep handle invariants for smooth and symmetric nodes,
* but smooth nodes at ends of linear segments and auto nodes need special treatment */
// move the involved handlers, first the node ones, later the adjoining ones
_pm().BSplineNodeHandlesReposition(this);
}
}
{
return b;
}
{
/* This method restores handle invariants for neighboring nodes,
* and invariants that are based on positions of those nodes for this one. */
/* Fix auto handles */
}
/* Fix smooth handles at the ends of linear segments.
* Rotate the appropriate handle to be colinear with the segment.
* If there is a smooth node at the other end of the segment, rotate it too. */
if (_is_line_segment(this, _next())) {
} else if (_is_line_segment(_prev(), this)) {
} else return;
}
// also update the handle on the other end of the segment
}
}
void Node::_updateAutoHandles()
{
// Recompute the position of automatic handles.
// For endnodes, retract both handles. (It's only possible to create an end auto node
// through the XML editor.)
if (isEndNode()) {
return;
}
// Auto nodes automaticaly adjust their handles to give an appearance of smoothness,
// no matter what their surroundings are.
// "dir" is an unit vector perpendicular to the bisector of the angle created
// by the previous node, this auto node and the next node.
// Handle lengths are equal to 1/3 of the distance from the adjacent node.
} else {
// If any of the adjacent nodes coincides, retract both handles.
}
}
void Node::showHandles(bool v)
{
_handles_shown = v;
if (!_front.isDegenerate()) {
_front.setVisible(v);
}
if (!_back.isDegenerate()) {
_back.setVisible(v);
}
// define the node strength, depending on being or not bspline traced.
// every time we operate over these handlers in a trace bspline
// that strength needs to be updated.
this->bsplineWeight = 0.0000;
if (!_front.isDegenerate()) {
}
if (!_back.isDegenerate()) {
}
}
}
void Node::updateHandles()
{
}
{
if (type == NODE_PICK_BEST) {
pickBestType();
updateState(); // The size of the control might have changed
return;
}
// if update_handles is true, adjust handle positions to match the node type
// handle degenerate handles appropriately
if (update_handles) {
switch (type) {
case NODE_CUSP:
// nothing to do
break;
case NODE_AUTO:
// auto handles make no sense for endnodes
if (isEndNode()) return;
break;
case NODE_SMOOTH: {
// ignore attempts to make smooth endnodes.
if (isEndNode()) return;
// rotate handles to be colinear
// for degenerate nodes set positions like auto handles
if (_type == NODE_SMOOTH) {
// For a node that is already smooth and has a degenerate handle,
// drag out the second handle without changing the direction of the first one.
if (_front.isDegenerate()) {
}
if (_back.isDegenerate()) {
}
} else if (isDegenerate()) {
} else if (_front.isDegenerate()) {
// if the front handle is degenerate and this...next is a line segment,
// make back colinear; otherwise pull out the other handle
// to 1/3 of distance to prev
if (next_line) {
} else if (_prev()) {
}
} else if (_back.isDegenerate()) {
if (prev_line) {
} else if (_next()) {
}
} else {
// both handles are extended. make colinear while keeping length
// first make back colinear with the vector front ---> back,
// then make front colinear with back ---> node
// (not back ---> front because back's position was changed in the first call)
}
} break;
case NODE_SYMMETRIC:
if (isEndNode()) return; // symmetric handles make no sense for endnodes
if (isDegenerate()) {
// similar to auto handles but set the same length for both
if (len == 0) return;
} else {
// Both handles are extended. Compute average length, use direction from
// back handle to front handle. This also works correctly for degenerates
}
break;
default: break;
}
/* in node type changes, about bspline traces, we can mantain them with 0.0000 power in border mode,
or we give them the default power in curve mode */
}
}
updateState();
}
void Node::pickBestType()
{
do {
// if both handles are degenerate, do nothing
if (both_degen) break;
// if neither are degenerate, check their respective positions
if (neither_degen) {
// for now do not automatically make nodes symmetric, it can be annoying
/*if (Geom::are_near(front_delta, -back_delta)) {
_type = NODE_SYMMETRIC;
break;
}*/
{
_type = NODE_SMOOTH;
break;
}
}
// check whether the handle aligns with the previous line segment.
// we know that if front is degenerate, back isn't, because
// both_degen was false
_type = NODE_SMOOTH;
break;
}
_type = NODE_SMOOTH;
break;
}
}
} while (false);
updateState();
}
{
}
{
}
{
switch (x) {
case 'a': return NODE_AUTO;
case 'c': return NODE_CUSP;
case 's': return NODE_SMOOTH;
case 'z': return NODE_SYMMETRIC;
default: return NODE_PICK_BEST;
}
}
{
int dir = 0;
{
case GDK_SCROLL:
dir = 1;
dir = -1;
} else break;
} else {
}
return true;
case GDK_KEY_PRESS:
{
case GDK_KEY_Page_Up:
dir = 1;
break;
case GDK_KEY_Page_Down:
dir = -1;
break;
default: goto bail_out;
}
} else {
}
return true;
default:
break;
}
}
{
// Interestingly, we do not need any help from PathManipulator when doing linear grow.
// First handle the trivial case of growing over an unselected node.
_selection.insert(this);
return;
}
double distance_back = 0, distance_front = 0;
// Linear grow is simple. We find the first unselected nodes in each direction
// and compare the linear distances to them.
if (dir > 0) {
if (!selected()) {
_selection.insert(this);
return;
}
// find first unselected nodes on both sides
fwd = n;
// there is no unselected node in this cyclic subpath
return;
}
// do the same for the second direction. Do not check for equality with
// this node, because there is at least one unselected node in the subpath,
// so we are guaranteed to stop.
rev = p;
}
else t = rev;
} else {
}
// Linear shrink is more complicated. We need to find the farthest selected node.
// This means we have to check the entire subpath. We go in the direction in which
// the distance we traveled is lower. We do this until we run out of nodes (ends of path)
// or the two iterators meet. On the way, we store the last selected node and its distance
// in each direction (if any). At the end, we choose the one that is farther and deselect it.
} else {
// both iterators that store last selected nodes are initially empty
double last_distance_back = 0, last_distance_front = 0;
}
fwd = n;
}
rev = p;
}
// Check whether we walked the entire cyclic subpath.
// This is initially true because both iterators start from this node,
// so this check cannot go in the while condition.
// When this happens, we need to check the last node, pointed to by the iterators.
} else {
}
break;
}
}
else t = last_rev;
} else {
}
}
}
{
// change node size to match type and selection state
switch (state) {
case STATE_NORMAL:
break;
case STATE_MOUSEOVER:
break;
case STATE_CLICKED:
//this shows the handlers when selecting the nodes
if(!this->back()->isDegenerate()){
}else if (!this->front()->isDegenerate()){
}else{
this->bsplineWeight = 0.0000;
}
if(!this->front()->isDegenerate()){
}
if(!this->back()->isDegenerate()){
}
}
break;
}
}
{
return true;
}
// Dragging out handles with Shift + drag on a node.
if (!held_shift(*event)) {
return false;
}
// This should work even if dragtolerance is zero and evp coincides with node position.
double angle_next = HUGE_VAL;
double angle_prev = HUGE_VAL;
bool has_degenerate = false;
// determine which handle to drag out based on degeneration and the direction of drag
has_degenerate = true;
}
has_degenerate = true;
}
if (!has_degenerate) {
return false;
}
h->setVisible(true);
h->transferGrab(this, event);
return true;
}
{
// For a note on how snapping is implemented in Inkscape, see snap.h.
// even if we won't really snap, we might still call the one of the
// constrainedSnap() methods to enforce the constraints, so we need
// to setup the snapmanager anyway; this is also required for someSnapperMightSnap()
// do not snap when Shift is pressed
if (snap) {
/* setup
* TODO We are doing this every time a snap happens. It should once be done only once
* per drag - maybe in the grabbed handler?
* TODO Unselected nodes vector must be valid during the snap run, because it is not
* TODO Snapping to unselected segments of selected paths doesn't work yet. */
// Build the list of unselected nodes.
if (!(*i)->selected()) {
unselected.push_back(p);
}
}
}
// Snap candidate point for free snapping; this will consider snapping tangentially
// and perpendicularly and therefore the origin or direction vector must be set
if (_front.isDegenerate()) {
if (_is_line_segment(this, _next())) {
} else {
}
}
} else {
}
if (_back.isDegenerate()) {
if (_is_line_segment(_prev(), this)) {
} else {
}
}
} else {
}
if (held_control(*event)) {
// We're about to consider a constrained snap, which is already limited to 1D
// Therefore tangential or perpendicular snapping will not be considered, and therefore
// all calls above to scp_free.addVector() and scp_free.addOrigin() can be neglected
// with Ctrl+Alt, constrain to handle lines
// project the new position onto a handle line that is closer;
// also snap to perpendiculars of handle lines
if (front_point) {
}
if (back_point) {
}
// perpendiculars only snap when they are further than snap increment away
// from the second handle constraint
if (fperp_point && (!back_point ||
{
}
if (bperp_point && (!front_point ||
{
}
sp = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), constraints, held_shift(*event));
} else {
// with Ctrl, constrain to axes
sp = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), constraints, held_shift(*event));
}
} else if (snap) {
}
}
{
return true;
}
{
return SNAPSOURCE_NODE_SMOOTH;
return SNAPSOURCE_NODE_CUSP;
}
{
return SNAPTARGET_NODE_SMOOTH;
return SNAPTARGET_NODE_CUSP;
}
{
}
{
return front();
}
return back();
}
g_error("Node::handleToward(): second node is not adjacent!");
}
{
return _next();
}
return _prev();
}
g_error("Node::nodeToward(): handle is not a child of this node!");
}
{
return back();
}
return front();
}
g_error("Node::handleAwayFrom(): second node is not adjacent!");
}
{
if (front() == h) {
return _prev();
}
if (back() == h) {
return _next();
}
g_error("Node::nodeAwayFrom(): handle is not a child of this node!");
}
{
/* if the node doesnt have strength, it marks it as bspline, we'll use it later
to show the appropiate messages. We cannot do it in any other way, because the
function is constant */
bool isBSpline = false;
if( this->bsplineWeight != 0.0000)
isBSpline = true;
if (state_held_shift(state)) {
if (can_drag_out) {
/*if (state_held_control(state)) {
return format_tip(C_("Path node tip",
"<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
"to %f° increments"), snap_increment_degrees());
}*/
return C_("Path node tip",
"<b>Shift</b>: drag out a handle, click to toggle selection");
}
}
if (state_held_control(state)) {
if (state_held_alt(state)) {
}
return C_("Path node tip",
"<b>Ctrl</b>: move along axes, click to change node type");
}
if (state_held_alt(state)) {
}
// No modifiers: assemble tip from node type
"<b>%s</b>: drag to shape the path (more: Shift, Ctrl, Alt)"), nodetype);
"<b>BSpline node</b>: %g weight, drag to shape the path (more: Shift, Ctrl, Alt)"),this->bsplineWeight);
}
"<b>%s</b>: drag to shape the path, click to toggle scale/rotation handles (more: Shift, Ctrl, Alt)"), nodetype);
}
if (!isBSpline) {
"<b>%s</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt)"), nodetype);
}else{
"<b>BSpline node</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt)"));
}
}
{
g_string_free(x, TRUE);
g_string_free(y, TRUE);
return ret;
}
{
switch (type) {
case NODE_CUSP: return _("Cusp node");
case NODE_SMOOTH: return _("Smooth node");
case NODE_SYMMETRIC: return _("Symmetric node");
case NODE_AUTO: return _("Auto-smooth node");
default: return "";
}
}
{
return false;
}
, _closed(false)
{
this->ln_list = this;
this->ln_next = this;
this->ln_prev = this;
}
{
clear();
}
{
return ln_next == this;
}
{
return sz;
}
{
return _closed;
}
bool NodeList::degenerate()
{
}
{
double intpart;
return ret;
}
{
x->ln_list = this;
return iterator(x);
}
{
}
{
++j;
}
{
}
}
{
// 1. make the list perfectly cyclic
// 2. find new begin
if (n > 0) {
} else {
}
// 3. relink begin to list
}
{
}
}
{
}
{
// some gymnastics are required to ensure that the node is valid when deleted;
// otherwise the code that updates handle visibility will break
++i;
delete rm;
return i;
}
// TODO this method is very ugly!
// converting SubpathList to an intrusive list might allow us to get rid of it
{
if (i->get() == this) {
return;
}
}
}
return n->nodeList();
}
}
} // namespace UI
} // namespace Inkscape
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :