You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1154 lines
20 KiB
1154 lines
20 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2002, The Karbon Developers
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
|
|
#include <math.h>
|
|
|
|
#include <tqdom.h>
|
|
#include <tqvaluelist.h>
|
|
#include <tqwmatrix.h>
|
|
|
|
#include "vpath.h"
|
|
#include "vsegment.h"
|
|
#include "vvisitor.h"
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
class VSubpathIteratorList
|
|
{
|
|
public:
|
|
VSubpathIteratorList()
|
|
: m_list( 0L ), m_iterator( 0L )
|
|
{}
|
|
|
|
~VSubpathIteratorList()
|
|
{
|
|
notifyClear( true );
|
|
delete m_list;
|
|
}
|
|
|
|
void add( VSubpathIterator* itr )
|
|
{
|
|
if( !m_iterator )
|
|
m_iterator = itr;
|
|
else if( m_list )
|
|
m_list->push_front( itr );
|
|
else
|
|
{
|
|
m_list = new TQValueList<VSubpathIterator*>;
|
|
m_list->push_front( itr );
|
|
}
|
|
}
|
|
|
|
void remove( VSubpathIterator* itr )
|
|
{
|
|
if( m_iterator == itr )
|
|
m_iterator = 0L;
|
|
else if( m_list )
|
|
{
|
|
m_list->remove( itr );
|
|
|
|
if( m_list->isEmpty() )
|
|
{
|
|
delete m_list;
|
|
m_list = 0L;
|
|
}
|
|
}
|
|
}
|
|
|
|
void notifyClear( bool zeroList )
|
|
{
|
|
if( m_iterator )
|
|
{
|
|
if( zeroList )
|
|
m_iterator->m_list = 0L;
|
|
|
|
m_iterator->m_current = 0L;
|
|
}
|
|
|
|
if( m_list )
|
|
{
|
|
for(
|
|
TQValueList<VSubpathIterator*>::Iterator itr = m_list->begin();
|
|
itr != m_list->end();
|
|
++itr )
|
|
{
|
|
if( zeroList )
|
|
( *itr )->m_list = 0L;
|
|
|
|
( *itr )->m_current = 0L;
|
|
}
|
|
}
|
|
}
|
|
|
|
void notifyRemove( VSegment* segment, VSegment* current )
|
|
{
|
|
if( m_iterator )
|
|
{
|
|
if( m_iterator->m_current == segment )
|
|
m_iterator->m_current = current;
|
|
}
|
|
|
|
if( m_list )
|
|
{
|
|
for(
|
|
TQValueList<VSubpathIterator*>::Iterator itr = m_list->begin();
|
|
itr != m_list->end();
|
|
++itr )
|
|
{
|
|
if( ( *itr )->m_current == segment )
|
|
( *itr )->m_current = current;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
TQValueList<VSubpathIterator*>* m_list;
|
|
VSubpathIterator* m_iterator;
|
|
};
|
|
|
|
|
|
VSubpath::VSubpath( VObject* parent )
|
|
: VObject( parent )
|
|
{
|
|
m_isClosed = false;
|
|
|
|
m_first = m_last = m_current = 0L;
|
|
m_number = 0;
|
|
m_currentIndex = -1;
|
|
m_iteratorList = 0L;
|
|
|
|
// Add an initial segment.
|
|
append( new VSegment( 1 ) );
|
|
}
|
|
|
|
VSubpath::VSubpath( const VSubpath& list )
|
|
: VObject( list )
|
|
{
|
|
m_isClosed = list.isClosed();
|
|
|
|
m_first = m_last = m_current = 0L;
|
|
m_number = 0;
|
|
m_currentIndex = -1;
|
|
m_iteratorList = 0L;
|
|
|
|
VSegment* segment = list.m_first;
|
|
|
|
while( segment )
|
|
{
|
|
append( segment->clone() );
|
|
segment = segment->m_next;
|
|
}
|
|
}
|
|
|
|
VSubpath::VSubpath( const VSegment& segment )
|
|
: VObject( 0L )
|
|
{
|
|
m_isClosed = false;
|
|
|
|
m_first = m_last = m_current = 0L;
|
|
m_number = 0;
|
|
m_currentIndex = -1;
|
|
m_iteratorList = 0L;
|
|
|
|
// The segment is not a "begin" segment.
|
|
if( segment.prev() )
|
|
{
|
|
// Add an initial segment.
|
|
append( new VSegment( 1 ) );
|
|
|
|
// Move the "begin" segment to the new segment's previous knot.
|
|
moveTo( segment.prev()->knot() );
|
|
}
|
|
|
|
// Append a copy of the segment.
|
|
append( segment.clone() );
|
|
}
|
|
|
|
VSubpath::~VSubpath()
|
|
{
|
|
clear();
|
|
delete m_iteratorList;
|
|
}
|
|
|
|
const KoPoint&
|
|
VSubpath::currentPoint() const
|
|
{
|
|
return getLast()->knot();
|
|
}
|
|
|
|
bool
|
|
VSubpath::moveTo( const KoPoint& p )
|
|
{
|
|
// Move "begin" segment if path is still empty.
|
|
if( isEmpty() )
|
|
{
|
|
getLast()->setKnot( p );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
VSubpath::lineTo( const KoPoint& p )
|
|
{
|
|
if( isClosed() )
|
|
return false;
|
|
|
|
VSegment* s = new VSegment( 1 );
|
|
|
|
s->setDegree( 1 );
|
|
s->setKnot( p );
|
|
|
|
append( s );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VSubpath::curveTo(
|
|
const KoPoint& p1, const KoPoint& p2, const KoPoint& p3 )
|
|
{
|
|
if( isClosed() )
|
|
return false;
|
|
|
|
VSegment* s = new VSegment();
|
|
|
|
s->setDegree( 3 );
|
|
s->setPoint( 0, p1 );
|
|
s->setPoint( 1, p2 );
|
|
s->setPoint( 2, p3 );
|
|
|
|
append( s );
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VSubpath::curve1To( const KoPoint& p2, const KoPoint& p3 )
|
|
{
|
|
if( isClosed() )
|
|
return false;
|
|
|
|
VSegment* s = new VSegment();
|
|
|
|
s->setDegree( 3 );
|
|
s->setPoint( 0, currentPoint() );
|
|
s->setPoint( 1, p2 );
|
|
s->setPoint( 2, p3 );
|
|
|
|
append( s );
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VSubpath::curve2To( const KoPoint& p1, const KoPoint& p3 )
|
|
{
|
|
if( isClosed() )
|
|
return false;
|
|
|
|
VSegment* s = new VSegment();
|
|
|
|
s->setDegree( 3 );
|
|
s->setPoint( 0, p1 );
|
|
s->setPoint( 1, p3 );
|
|
s->setPoint( 2, p3 );
|
|
|
|
append( s );
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VSubpath::arcTo(
|
|
const KoPoint& p1, const KoPoint& p2, const double r )
|
|
{
|
|
/* This routine is inspired by code in GNU ghostscript.
|
|
*
|
|
* |- P1B3 -|
|
|
*
|
|
* |- - - T12- - -|
|
|
*
|
|
* - - P1 x....__--o.....x P2
|
|
* | | : _/ B3
|
|
* P1B0 : /
|
|
* | :/
|
|
* | |
|
|
* - T10 o B0
|
|
* |
|
|
* | |
|
|
* |
|
|
* | |
|
|
* - x P0
|
|
*/
|
|
|
|
if( isClosed() || r < 0.0 )
|
|
return false;
|
|
|
|
|
|
// We need to calculate the tangent points. Therefore calculate tangents
|
|
// T10=P1P0 and T12=P1P2 first.
|
|
double dx0 = currentPoint().x() - p1.x();
|
|
double dy0 = currentPoint().y() - p1.y();
|
|
double dx2 = p2.x() - p1.x();
|
|
double dy2 = p2.y() - p1.y();
|
|
|
|
// Calculate distance squares.
|
|
double dsqT10 = dx0 * dx0 + dy0 * dy0;
|
|
double dsqT12 = dx2 * dx2 + dy2 * dy2;
|
|
|
|
// We now calculate tan(a/2) where a is the angle between T10 and T12.
|
|
// We benefit from the facts T10*T12 = |T10|*|T12|*cos(a),
|
|
// |T10xT12| = |T10|*|T12|*sin(a) (cross product) and tan(a/2) = sin(a)/[1-cos(a)].
|
|
double num = dy0 * dx2 - dy2 * dx0;
|
|
|
|
double denom = sqrt( dsqT10 * dsqT12 ) - ( dx0 * dx2 + dy0 * dy2 );
|
|
|
|
// The points are colinear.
|
|
if( 1.0 + denom == 1.0 )
|
|
{
|
|
// Just add a line.
|
|
lineTo( p1 );
|
|
}
|
|
else
|
|
{
|
|
// |P1B0| = |P1B3| = r * tan(a/2).
|
|
double dP1B0 = fabs( r * num / denom );
|
|
|
|
// B0 = P1 + |P1B0| * T10/|T10|.
|
|
KoPoint b0 = p1 + KoPoint( dx0, dy0 ) * ( dP1B0 / sqrt( dsqT10 ) );
|
|
|
|
// If B0 deviates from current point P0, add a line to it.
|
|
if( !b0.isNear( currentPoint(), VGlobal::isNearRange ) )
|
|
lineTo( b0 );
|
|
|
|
// B3 = P1 + |P1B3| * T12/|T12|.
|
|
KoPoint b3 = p1 + KoPoint( dx2, dy2 ) * ( dP1B0 / sqrt( dsqT12 ) );
|
|
|
|
|
|
// The two bezier-control points are located on the tangents at a fraction
|
|
// of the distance[ tangent points <-> tangent intersection ].
|
|
const KoPoint d = p1 - b0;
|
|
|
|
double distsq = d * d;
|
|
|
|
double rsq = r * r;
|
|
|
|
double fract;
|
|
|
|
// r is very small.
|
|
if( distsq >= rsq * VGlobal::veryBigNumber )
|
|
{
|
|
// Assume dist = r = 0.
|
|
fract = 0.0;
|
|
}
|
|
else
|
|
{
|
|
fract = ( 4.0 / 3.0 ) / ( 1.0 + sqrt( 1.0 + distsq / rsq ) );
|
|
}
|
|
|
|
KoPoint b1 = b0 + ( p1 - b0 ) * fract;
|
|
KoPoint b2 = b3 + ( p1 - b3 ) * fract;
|
|
|
|
// Finally add the bezier-segment.
|
|
curveTo( b1, b2, b3 );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VSubpath::close()
|
|
{
|
|
// In the case the list is 100% empty (which should actually never happen),
|
|
// append a "begin" first, to avoid a crash.
|
|
if( count() == 0 )
|
|
append( new VSegment( 1 ) );
|
|
|
|
// Move last segment if we are already closed.
|
|
if( isClosed() )
|
|
{
|
|
getLast()->setKnot( getFirst()->knot() );
|
|
}
|
|
// Append a line, if necessary.
|
|
else
|
|
{
|
|
if(
|
|
getLast()->knot().isNear(
|
|
getFirst()->knot(), VGlobal::isNearRange ) )
|
|
{
|
|
// Move last knot.
|
|
getLast()->setKnot( getFirst()->knot() );
|
|
}
|
|
else
|
|
{
|
|
// Add a line.
|
|
lineTo( getFirst()->knot() );
|
|
}
|
|
|
|
m_isClosed = true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
VSubpath::pointIsInside( const KoPoint& p ) const
|
|
{
|
|
// If the point is not inside the boundingbox, it cannot be inside the path either.
|
|
if( !boundingBox().contains( p ) )
|
|
return false;
|
|
|
|
// First check if the point is inside the knot polygon (beziers are treated
|
|
// as lines).
|
|
|
|
/* This algorithm is taken from "Fast Winding Number Inclusion of a Point
|
|
* in a Polygon" by Dan Sunday, geometryalgorithms.com.
|
|
*/
|
|
|
|
/*
|
|
int windingNumber = 0;
|
|
|
|
// Ommit first segment.
|
|
VSegment* segment = getFirst()->next();
|
|
|
|
while( segment )
|
|
{
|
|
if( segment->prev()->knot().y() <= p.y() )
|
|
{
|
|
// Upward crossing.
|
|
if( segment->knot().y() > p.y() )
|
|
{
|
|
// Point is left.
|
|
if( segment->pointIsLeft( p ) > 0 )
|
|
{
|
|
// Valid up intersection.
|
|
++windingNumber;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Downward crossing.
|
|
if( segment->knot().y() <= p.y() )
|
|
{
|
|
// Point is right.
|
|
if( segment->pointIsLeft( p ) < 0 )
|
|
{
|
|
// Valid down intersection.
|
|
--windingNumber;
|
|
}
|
|
}
|
|
}
|
|
|
|
segment = segment->next();
|
|
}
|
|
|
|
if( static_cast<bool>( windingNumber ) )
|
|
return true;
|
|
*/
|
|
|
|
// Then check if the point is located in between the knot polygon
|
|
// and the bezier curves.
|
|
|
|
/* We rotate each segment in order to make their chord (the line between
|
|
* the previous knot and the knot ) parallel to the x-axis. Then we
|
|
* calculate y(xp) on the segment for the rotated input point (xp,yp)
|
|
* and compare y(xp) with yp.
|
|
*/
|
|
// TODO
|
|
|
|
// cache the closed evaluation
|
|
bool closed = isClosed() || getLast()->knot() == getFirst()->knot();
|
|
|
|
TQValueList<double> rparams;
|
|
|
|
VSegment* segment = getFirst()->next();
|
|
|
|
// move all segements so that p is the origin
|
|
// and compute their intersections with the x-axis
|
|
while( segment )
|
|
{
|
|
VSubpath tmpCurve( 0L );
|
|
tmpCurve.append( new VSegment( segment->degree() ) );
|
|
|
|
for( int i = 0; i <= segment->degree(); ++i )
|
|
tmpCurve.current()->setP(i, segment->p(i)-p );
|
|
|
|
tmpCurve.current()->rootParams( rparams );
|
|
|
|
segment = segment->next();
|
|
}
|
|
|
|
// if the path is not closed, compute the intersection of
|
|
// the line through the first and last knot and the x-axis too
|
|
if( ! closed )
|
|
{
|
|
KoPoint prevKnot = getLast()->knot() - p;
|
|
KoPoint nextKnot = getFirst()->knot() - p;
|
|
|
|
double dx = nextKnot.x() - prevKnot.x();
|
|
double dy = nextKnot.y() - prevKnot.y();
|
|
if( dx == 0.0 )
|
|
{
|
|
rparams.append( nextKnot.x() );
|
|
}
|
|
else if( dy != 0.0 )
|
|
{
|
|
if( ( prevKnot.y() < 0.0 && nextKnot.y() > 0.0 ) || ( prevKnot.y() > 0.0 && nextKnot.y() < 0.0 ) )
|
|
{
|
|
double n = prevKnot.y() - dy / dx * prevKnot.x();
|
|
rparams.append( -n * dx / dy );
|
|
}
|
|
}
|
|
}
|
|
|
|
kdDebug(38000) << "intersection count: " << rparams.count() << endl;
|
|
|
|
// sort all intersections
|
|
qHeapSort( rparams );
|
|
|
|
TQValueList<double>::iterator itr, etr = rparams.end();
|
|
|
|
for( itr = rparams.begin(); itr != etr; ++itr )
|
|
kdDebug(38000) << "intersection: " << *itr << endl;
|
|
|
|
if( closed )
|
|
{
|
|
// pair the intersections and check if the origin is within a pair
|
|
for( itr = rparams.begin(); itr != etr; ++itr )
|
|
{
|
|
if( *itr > 0.0 )
|
|
return false;
|
|
|
|
if( ++itr == etr )
|
|
return false;
|
|
|
|
if( *itr > 0.0 )
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only check if point is between first and last intersection if we have an open path
|
|
if ( rparams.front() < 0.0 && rparams.back() > 0.0 )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
VSubpath::intersects( const VSegment& s ) const
|
|
{
|
|
// Check if path is empty and if boundingboxes intersect.
|
|
if(
|
|
isEmpty() ||
|
|
!boundingBox().intersects( s.boundingBox() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// Ommit first segment.
|
|
VSegment* segment = getFirst()->next();
|
|
|
|
while( segment )
|
|
{
|
|
if( segment->intersects( s ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
segment = segment->next();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
VSubpath::counterClockwise() const
|
|
{
|
|
/* This algorithm is taken from the FAQ of comp.graphics.algorithms:
|
|
* "Find the lowest vertex (or, if there is more than one vertex with the
|
|
* same lowest coordinate, the rightmost of those vertices) and then take
|
|
* the cross product of the edges fore and aft of it."
|
|
*/
|
|
|
|
// A non closed path does not have a winding.
|
|
if( !isClosed() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
VSegment* segment = getFirst();
|
|
|
|
// We save the segment not the knot itself. Initialize it with the
|
|
// first segment:
|
|
const VSegment* bottomRight = getFirst();
|
|
|
|
while( segment )
|
|
{
|
|
if( segment->knot().y() < bottomRight->knot().y() )
|
|
bottomRight = segment;
|
|
else if( segment->knot().y() - bottomRight->knot().y()
|
|
< VGlobal::isNearRange )
|
|
{
|
|
if( segment->knot().x() > bottomRight->knot().x() )
|
|
bottomRight = segment;
|
|
}
|
|
|
|
segment = segment->next();
|
|
}
|
|
|
|
|
|
// Catch boundary case (bottomRight is first or last segment):
|
|
const VSegment* current;
|
|
const VSegment* next;
|
|
|
|
if( bottomRight == getFirst() )
|
|
current = getLast();
|
|
else
|
|
current = bottomRight;
|
|
|
|
if( bottomRight == getLast() )
|
|
next = getFirst()->next();
|
|
else
|
|
next = bottomRight->next();
|
|
|
|
// Check "z-component" of cross product:
|
|
return
|
|
( next->knot().x() - next->prev()->knot().x() ) *
|
|
( current->knot().y() - current->prev()->knot().y() )
|
|
-
|
|
( next->knot().y() - next->prev()->knot().y() ) *
|
|
( current->knot().x() - current->prev()->knot().x() ) < 0.0;
|
|
}
|
|
|
|
void
|
|
VSubpath::revert()
|
|
{
|
|
// Catch case where the list is "empty".
|
|
if( isEmpty() )
|
|
return;
|
|
|
|
|
|
VSubpath list( parent() );
|
|
list.moveTo( getLast()->knot() );
|
|
|
|
VSegment* segment = getLast();
|
|
|
|
while( segment->prev() )
|
|
{
|
|
list.append( segment->revert() );
|
|
segment = segment->prev();
|
|
}
|
|
|
|
list.m_isClosed = isClosed();
|
|
|
|
*this = list;
|
|
}
|
|
|
|
const KoRect&
|
|
VSubpath::boundingBox() const
|
|
{
|
|
if( m_boundingBoxIsInvalid )
|
|
{
|
|
// Reset the boundingbox.
|
|
m_boundingBox = KoRect();
|
|
|
|
VSegment* segment = m_first;
|
|
|
|
while( segment )
|
|
{
|
|
if( segment->state() != VSegment::deleted )
|
|
m_boundingBox |= segment->boundingBox();
|
|
|
|
segment = segment->m_next;
|
|
}
|
|
|
|
m_boundingBoxIsInvalid = false;
|
|
}
|
|
|
|
return m_boundingBox;
|
|
}
|
|
|
|
VSubpath*
|
|
VSubpath::clone() const
|
|
{
|
|
return new VSubpath( *this );
|
|
}
|
|
|
|
void
|
|
VSubpath::saveSvgPath( TQString &d ) const
|
|
{
|
|
// Save segments.
|
|
VSegment* segment = getFirst();
|
|
|
|
while( segment )
|
|
{
|
|
if( segment->state() == VSegment::normal )
|
|
{
|
|
if( segment->degree() <= 2 )
|
|
{
|
|
// Line.
|
|
if( segment->prev() )
|
|
{
|
|
d += TQString( "L%1 %2" ).
|
|
arg( segment->knot().x() ).arg( segment->knot().y() );
|
|
}
|
|
// Moveto.
|
|
else
|
|
{
|
|
d += TQString( "M%1 %2" ).
|
|
arg( segment->knot().x() ).arg( segment->knot().y() );
|
|
}
|
|
}
|
|
// Bezier ( degree >= 3 ).
|
|
else
|
|
{
|
|
// We currently treat all beziers as cubic beziers.
|
|
d += TQString( "C%1 %2 %3 %4 %5 %6" ).
|
|
arg( segment->point( segment->degree() - 3 ).x() ).
|
|
arg( segment->point( segment->degree() - 3 ).y() ).
|
|
arg( segment->point( segment->degree() - 2 ).x() ).
|
|
arg( segment->point( segment->degree() - 2 ).y() ).
|
|
arg( segment->knot().x() ).
|
|
arg( segment->knot().y() );
|
|
}
|
|
}
|
|
|
|
segment = segment->m_next;
|
|
}
|
|
|
|
if( isClosed() )
|
|
d += "Z";
|
|
}
|
|
|
|
// TODO: remove this backward compatibility function after koffice 1.3.x
|
|
void
|
|
VSubpath::load( const TQDomElement& element )
|
|
{
|
|
// We might have a "begin" segment.
|
|
clear();
|
|
|
|
TQDomNodeList list = element.childNodes();
|
|
|
|
for( uint i = 0; i < list.count(); ++i )
|
|
{
|
|
if( list.item( i ).isElement() )
|
|
{
|
|
TQDomElement segment = list.item( i ).toElement();
|
|
|
|
VSegment* s = new VSegment();
|
|
s->load( segment );
|
|
append( s );
|
|
}
|
|
}
|
|
|
|
if( element.attribute( "isClosed" ) == 0 ? false : true )
|
|
close();
|
|
}
|
|
|
|
void
|
|
VSubpath::accept( VVisitor& visitor )
|
|
{
|
|
visitor.visitVSubpath( *this );
|
|
}
|
|
|
|
|
|
VSubpath&
|
|
VSubpath::operator=( const VSubpath& list )
|
|
{
|
|
if( this == &list )
|
|
return *this;
|
|
|
|
m_isClosed = list.isClosed();
|
|
|
|
clear();
|
|
|
|
VSegment* segment = list.m_first;
|
|
|
|
while( segment )
|
|
{
|
|
append( segment->clone() );
|
|
segment = segment->m_next;
|
|
}
|
|
|
|
m_current = m_first;
|
|
m_currentIndex = 0;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool
|
|
VSubpath::insert( const VSegment* segment )
|
|
{
|
|
if( m_currentIndex == -1 )
|
|
return false;
|
|
|
|
VSegment* s = const_cast<VSegment*>( segment );
|
|
|
|
VSegment* prev = m_current->m_prev;
|
|
|
|
m_current->m_prev = s;
|
|
prev->m_next = s;
|
|
s->m_prev = prev;
|
|
s->m_next = m_current;
|
|
|
|
m_current = s;
|
|
++m_number;
|
|
|
|
invalidateBoundingBox();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VSubpath::insert( uint index, const VSegment* segment )
|
|
{
|
|
VSegment* s = const_cast<VSegment*>( segment );
|
|
|
|
if( index == 0 )
|
|
{
|
|
prepend( s );
|
|
return true;
|
|
}
|
|
else if( index == m_number )
|
|
{
|
|
append( s );
|
|
return true;
|
|
}
|
|
|
|
VSegment* next = locate( index );
|
|
|
|
if( !next )
|
|
return false;
|
|
|
|
VSegment* prev = next->m_prev;
|
|
|
|
next->m_prev = s;
|
|
prev->m_next = s;
|
|
s->m_prev = prev;
|
|
s->m_next = next;
|
|
|
|
m_current = s;
|
|
++m_number;
|
|
|
|
invalidateBoundingBox();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VSubpath::prepend( const VSegment* segment )
|
|
{
|
|
VSegment* s = const_cast<VSegment*>( segment );
|
|
|
|
s->m_prev = 0L;
|
|
|
|
if( ( s->m_next = m_first ) )
|
|
m_first->m_prev = s;
|
|
else
|
|
m_last = s;
|
|
|
|
m_first = m_current = s;
|
|
|
|
++m_number;
|
|
m_currentIndex = 0;
|
|
|
|
invalidateBoundingBox();
|
|
}
|
|
|
|
void
|
|
VSubpath::append( const VSegment* segment )
|
|
{
|
|
VSegment* s = const_cast<VSegment*>( segment );
|
|
|
|
s->m_next = 0L;
|
|
|
|
if( ( s->m_prev = m_last ) )
|
|
m_last->m_next = s;
|
|
else
|
|
m_first = s;
|
|
|
|
m_last = m_current = s;
|
|
|
|
m_currentIndex = m_number;
|
|
++m_number;
|
|
|
|
invalidateBoundingBox();
|
|
}
|
|
|
|
void
|
|
VSubpath::clear()
|
|
{
|
|
VSegment* segment = m_first;
|
|
|
|
m_first = m_last = m_current = 0L;
|
|
m_number = 0;
|
|
m_currentIndex = -1;
|
|
|
|
if( m_iteratorList )
|
|
m_iteratorList->notifyClear( false );
|
|
|
|
VSegment* prev;
|
|
|
|
while( segment )
|
|
{
|
|
prev = segment;
|
|
segment = segment->m_next;
|
|
delete prev;
|
|
}
|
|
|
|
m_isClosed = false;
|
|
|
|
invalidateBoundingBox();
|
|
}
|
|
|
|
VSegment*
|
|
VSubpath::first()
|
|
{
|
|
if( m_first )
|
|
{
|
|
m_currentIndex = 0;
|
|
return m_current = m_first;
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpath::last()
|
|
{
|
|
if( m_last )
|
|
{
|
|
m_currentIndex = m_number - 1;
|
|
return m_current = m_last;
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpath::prev()
|
|
{
|
|
if( m_current )
|
|
{
|
|
if( m_current->m_prev )
|
|
{
|
|
--m_currentIndex;
|
|
return m_current = m_current->m_prev;
|
|
}
|
|
|
|
m_currentIndex = -1;
|
|
m_current = 0L;
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpath::next()
|
|
{
|
|
if( m_current )
|
|
{
|
|
if( m_current->m_next )
|
|
{
|
|
++m_currentIndex;
|
|
return m_current = m_current->m_next;
|
|
}
|
|
|
|
m_currentIndex = -1;
|
|
m_current = 0L;
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpath::locate( uint index )
|
|
{
|
|
if( index == static_cast<uint>( m_currentIndex ) )
|
|
return m_current;
|
|
|
|
if( !m_current && m_first )
|
|
{
|
|
m_current = m_first;
|
|
m_currentIndex = 0;
|
|
}
|
|
|
|
VSegment* segment;
|
|
int distance = index - m_currentIndex;
|
|
bool forward;
|
|
|
|
if( index >= m_number )
|
|
return 0L;
|
|
|
|
if( distance < 0 )
|
|
distance = -distance;
|
|
|
|
if(
|
|
static_cast<uint>( distance ) < index &&
|
|
static_cast<uint>( distance ) < m_number - index )
|
|
{
|
|
segment = m_current;
|
|
forward = index > static_cast<uint>( m_currentIndex );
|
|
}
|
|
else if( index < m_number - index )
|
|
{
|
|
segment = m_first;
|
|
distance = index;
|
|
forward = true;
|
|
}
|
|
else
|
|
{
|
|
segment = m_last;
|
|
distance = m_number - index - 1;
|
|
if( distance < 0 )
|
|
distance = 0;
|
|
forward = false;
|
|
}
|
|
|
|
if( forward )
|
|
{
|
|
while( distance-- )
|
|
segment = segment->m_next;
|
|
}
|
|
else
|
|
{
|
|
while( distance-- )
|
|
segment = segment->m_prev;
|
|
}
|
|
|
|
m_currentIndex = index;
|
|
return m_current = segment;
|
|
}
|
|
|
|
|
|
VSubpathIterator::VSubpathIterator( const VSubpath& list )
|
|
{
|
|
m_list = const_cast<VSubpath*>( &list );
|
|
m_current = m_list->m_first;
|
|
|
|
if( !m_list->m_iteratorList )
|
|
m_list->m_iteratorList = new VSubpathIteratorList();
|
|
|
|
m_list->m_iteratorList->add( this );
|
|
}
|
|
|
|
VSubpathIterator::VSubpathIterator( const VSubpathIterator& itr )
|
|
{
|
|
m_list = itr.m_list;
|
|
m_current = itr.m_current;
|
|
|
|
if( m_list )
|
|
m_list->m_iteratorList->add( this );
|
|
}
|
|
|
|
VSubpathIterator::~VSubpathIterator()
|
|
{
|
|
if( m_list )
|
|
m_list->m_iteratorList->remove( this );
|
|
}
|
|
|
|
VSubpathIterator&
|
|
VSubpathIterator::operator=( const VSubpathIterator& itr )
|
|
{
|
|
if( m_list )
|
|
m_list->m_iteratorList->remove( this );
|
|
|
|
m_list = itr.m_list;
|
|
m_current = itr.m_current;
|
|
|
|
if( m_list )
|
|
m_list->m_iteratorList->add( this );
|
|
|
|
return *this;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::current() const
|
|
{
|
|
// If m_current points to a deleted segment, find the next not
|
|
// deleted segment.
|
|
if(
|
|
m_current &&
|
|
m_current->state() == VSegment::deleted )
|
|
{
|
|
return m_current->next();
|
|
}
|
|
|
|
return m_current;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::operator()()
|
|
{
|
|
if( VSegment* const old = current() )
|
|
{
|
|
m_current = current()->next();
|
|
return old;
|
|
}
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::operator++()
|
|
{
|
|
if( current() )
|
|
return m_current = current()->next();
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::operator+=( uint i )
|
|
{
|
|
while( current() && i-- )
|
|
m_current = current()->next();
|
|
|
|
return current();
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::operator--()
|
|
{
|
|
if( current() )
|
|
return m_current = current()->prev();
|
|
|
|
return 0L;
|
|
}
|
|
|
|
VSegment*
|
|
VSubpathIterator::operator-=( uint i )
|
|
{
|
|
while( current() && i-- )
|
|
m_current = current()->prev();
|
|
|
|
return current();
|
|
}
|
|
|