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.
tdeedu/kig/misc/coordinate_system.cpp

721 lines
21 KiB

/**
This file is part of Kig, a KDE program for Interactive Geometry...
Copyright (C) 2002 Dominique Devriese <devriese@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
**/
#include "coordinate_system.h"
#include "../kig/kig_document.h"
#include "../kig/kig_view.h"
#include "common.h"
#include "coordinate.h"
#include "goniometry.h"
#include "kigpainter.h"
#include <tqpainter.h>
#include <tqregexp.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <knumvalidator.h>
#include <string>
#include <math.h>
class CoordinateValidator
: public TQValidator
{
bool mpolar;
#ifdef KIG_USE_KDOUBLEVALIDATOR
KDoubleValidator mdv;
#else
KFloatValidator mdv;
#endif
mutable TQRegExp mre;
public:
CoordinateValidator( bool polar );
~CoordinateValidator();
State validate ( TQString & input, int & pos ) const;
void fixup ( TQString & input ) const;
};
CoordinateValidator::CoordinateValidator( bool polar )
: TQValidator( 0, 0 ), mpolar( polar ), mdv( 0, 0 ),
mre( polar ? "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?<3F>? ?\\)?"
: "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?\\)?" )
{
}
CoordinateValidator::~CoordinateValidator()
{
}
TQValidator::State CoordinateValidator::validate( TQString & input, int & pos ) const
{
TQString tinput = input;
if ( tinput[tinput.length() - 1 ] == ')' ) tinput.truncate( tinput.length() - 1 );
if ( mpolar )
{
if ( tinput[tinput.length() - 1 ] == ' ' ) tinput.truncate( tinput.length() - 1 );
if ( tinput[tinput.length() - 1 ] == '<EFBFBD>' ) tinput.truncate( tinput.length() - 1 );
};
if( tinput[tinput.length() - 1 ] == ' ' ) tinput.truncate( tinput.length() - 1 );
if ( tinput[0] == '(' ) tinput = tinput.mid( 1 );
if( tinput[0] == ' ' ) tinput = tinput.mid( 1 );
int scp = tinput.tqfind( ';' );
if ( scp == -1 ) return mdv.validate( tinput, pos ) == Invalid ? Invalid : Valid;
else
{
TQString p1 = tinput.left( scp );
TQString p2 = tinput.mid( scp + 1 );
State ret = Acceptable;
int boguspos = 0;
ret = kigMin( ret, mdv.validate( p1, boguspos ) );
boguspos = 0;
ret = kigMin( ret, mdv.validate( p2, boguspos ) );
return ret;
};
}
void CoordinateValidator::fixup( TQString & input ) const
{
int nsc = input.tqcontains( ';' );
if ( nsc > 1 )
{
// where is the second ';'
int i = input.tqfind( ';' );
i = input.tqfind( ';', i );
input = input.left( i );
};
// now the string has at most one semicolon left..
int sc = input.tqfind( ';' );
if ( sc == -1 )
{
sc = input.length();
KLocale* l = KGlobal::locale();
if ( mpolar )
input.append( TQString::tqfromLatin1( ";" ) + l->positiveSign() +
TQString::tqfromLatin1( "0<EFBFBD>" ) );
else
input.append( TQString::tqfromLatin1( ";" ) + l->positiveSign() +
TQString::tqfromLatin1( "0" ) + l->decimalSymbol() +
TQString::tqfromLatin1( "0" ) );
};
mre.exactMatch( input );
TQString ds1 = mre.cap( 1 );
mdv.fixup( ds1 );
TQString ds2 = mre.cap( 2 );
mdv.fixup( ds2 );
input = ds1 + TQString::tqfromLatin1( "; " ) + ds2;
}
EuclideanCoords::EuclideanCoords()
{
}
TQString EuclideanCoords::fromScreen( const Coordinate& p, const KigDocument& d ) const
{
// i used to use the widget size here, but that's no good idea,
// since an object isn't asked to recalc every time the widget size
// changes.. might be a good idea to do that, but well, maybe some
// other time :)
Rect sr = d.suggestedRect();
double m = kigMax( sr.width(), sr.height() );
int l = kigMax( 0, (int) ( 3 - log10( m ) ) );
TQString xs = KGlobal::locale()->formatNumber( p.x, l );
TQString ys = KGlobal::locale()->formatNumber( p.y, l );
return TQString::tqfromLatin1( "( %1; %2 )" ).tqarg( xs ).tqarg( ys );
}
Coordinate EuclideanCoords::toScreen(const TQString& s, bool& ok) const
{
TQRegExp r( "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?\\)?" );
ok = ( r.search(s) == 0 );
if (ok)
{
TQString xs = r.cap(1);
TQString ys = r.cap(2);
KLocale* l = KGlobal::locale();
double x = l->readNumber( xs, &ok );
if ( ! ok ) x = xs.toDouble( &ok );
if ( ! ok ) return Coordinate();
double y = l->readNumber( ys, &ok );
if ( ! ok ) y = ys.toDouble( &ok );
if ( ! ok ) return Coordinate();
return Coordinate( x, y );
}
return Coordinate();
}
/**
* copied and adapted from a ( public domain ) function i found in the
* first Graphics Gems book. Credits to Paul S. Heckbert, who wrote
* the "Nice number for graph labels" gem.
* find a "nice" number approximately equal to x. We look for
* 1, 2 or 5, multiplied by a power of 10.
*/
static double nicenum( double x, bool round )
{
int exp = (int) log10( x );
double f = x/pow( 10., exp );
double nf;
if ( round )
{
if ( f < 1.5 ) nf = 1.;
else if ( f < 3. ) nf = 2.;
else if ( f < 7. ) nf = 5.;
else nf = 10.;
}
else
{
if ( f <= 1. ) nf = 1.;
else if ( f <= 2. ) nf = 2.;
else if ( f <= 5. ) nf = 5.;
else nf = 10.;
};
return nf * pow( 10., exp );
}
void EuclideanCoords::drawGrid( KigPainter& p, bool showgrid, bool showaxes ) const
{
p.setWholeWinOverlay();
// this instruction in not necessary, but there is a little
// optimization when there are no grid and no axes.
if ( !( showgrid || showaxes ) )
return;
// this function is inspired upon ( public domain ) code from the
// first Graphics Gems book. Credits to Paul S. Heckbert, who wrote
// the "Nice number for graph labels" gem.
const double hmax = ceil( p.window().right() );
const double hmin = floor( p.window().left() );
const double vmax = ceil( p.window().top() );
const double vmin = floor( p.window().bottom() );
// the number of intervals we would like to have:
// we try to have one of them per 40 pixels or so..
const int ntick = static_cast<int>(
kigMax( hmax - hmin, vmax - vmin ) / p.pixelWidth() / 40. ) + 1;
double hrange = nicenum( hmax - hmin, false );
double vrange = nicenum( vmax - vmin, false );
const double newrange = kigMin( hrange, vrange );
hrange = newrange;
vrange = newrange;
const double hd = nicenum( hrange / ( ntick - 1 ), true );
const double vd = nicenum( vrange / ( ntick - 1 ), true );
const double hgraphmin = ceil( hmin / hd) * hd;
const double hgraphmax = floor( hmax / hd ) * hd;
const double vgraphmin = ceil( vmin / vd ) * vd;
const double vgraphmax = floor( vmax / vd ) * vd;
const int hnfrac = kigMax( (int) - floor( log10( hd ) ), 0 );
const int vnfrac = kigMax( (int) - floor( log10( vd ) ), 0 );
/****** the grid lines ******/
if ( showgrid )
{
p.setPen( TQPen( lightGray, 0, DotLine ) );
// vertical lines...
for ( double i = hgraphmin; i <= hgraphmax + hd/2; i += hd )
p.drawSegment( Coordinate( i, vgraphmin ),
Coordinate( i, vgraphmax ) );
// horizontal lines...
for ( double i = vgraphmin; i <= vgraphmax + vd/2; i += vd )
p.drawSegment( Coordinate( hgraphmin, i ),
Coordinate( hgraphmax, i ) );
}
/****** the axes ******/
if ( showaxes )
{
p.setPen( TQPen( TQt::gray, 1, TQt::SolidLine ) );
// x axis
p.drawSegment( Coordinate( hmin, 0 ), Coordinate( hmax, 0 ) );
// y axis
p.drawSegment( Coordinate( 0, vmin ), Coordinate( 0, vmax ) );
/****** the numbers ******/
// x axis
for( double i = hgraphmin; i <= hgraphmax + hd / 2; i += hd )
{
// we skip 0 since that would look stupid... (the axes going
// through the 0 etc. )
if( fabs( i ) < 1e-8 ) continue;
p.drawText(
Rect( Coordinate( i, 0 ), hd, -2*vd ).normalized(),
KGlobal::locale()->formatNumber( i, hnfrac ),
AlignLeft | AlignTop
);
};
// y axis...
for ( double i = vgraphmin; i <= vgraphmax + vd/2; i += vd )
{
if( fabs( i ) < 1e-8 ) continue;
p.drawText ( Rect( Coordinate( 0, i ), 2*hd, vd ).normalized(),
KGlobal::locale()->formatNumber( i, vnfrac ),
AlignBottom | AlignLeft
);
};
// arrows on the ends of the axes...
p.setPen( TQPen( TQt::gray, 1, TQt::SolidLine ) );
p.setBrush( TQBrush( TQt::gray ) );
std::vector<Coordinate> a;
// the arrow on the right end of the X axis...
a.reserve( 3 );
double u = p.pixelWidth();
a.push_back( Coordinate( hmax - 6 * u, -3 * u) );
a.push_back( Coordinate( hmax, 0 ) );
a.push_back( Coordinate( hmax - 6 * u, 3 * u ) );
p.drawArea( a );
// p.drawPolygon( a, true );
// the arrow on the top end of the Y axis...
a.clear();
a.reserve( 3 );
a.push_back( Coordinate( 3 * u, vmax - 6 * u ) );
a.push_back( Coordinate( 0, vmax ) );
a.push_back( Coordinate( -3 * u, vmax - 6 * u ) );
p.drawArea( a );
// p.drawPolygon( a, true );
}; // if( showaxes )
}
TQString EuclideanCoords::coordinateFormatNotice() const
{
return i18n( "Enter coordinates in the following format: \"x;y\",\n"
"where x is the x coordinate, and y is the y coordinate." );
}
TQString EuclideanCoords::coordinateFormatNoticeMarkup() const
{
return i18n( "Enter coordinates in the following format: <b>\"x;y\"</b>, "
"where x is the x coordinate, and y is the y coordinate." );
}
EuclideanCoords::~EuclideanCoords()
{
}
CoordinateSystem::~CoordinateSystem()
{
}
CoordinateSystem::CoordinateSystem()
{
}
PolarCoords::PolarCoords()
{
}
PolarCoords::~PolarCoords()
{
}
TQString PolarCoords::fromScreen( const Coordinate& pt, const KigDocument& d ) const
{
Rect sr = d.suggestedRect();
double m = kigMax( sr.width(), sr.height() );
int l = kigMax( 0, (int) ( 3 - log10( m ) ) );
double r = pt.length();
double theta = Goniometry::convert( atan2( pt.y, pt.x ), Goniometry::Rad, Goniometry::Deg );
TQString rs = KGlobal::locale()->formatNumber( r, l );
TQString ts = KGlobal::locale()->formatNumber( theta, 0 );
return TQString::tqfromLatin1("( %1; %2<> )").tqarg( rs ).tqarg( ts );
}
TQString PolarCoords::coordinateFormatNotice() const
{
// \xCE\xB8 is utf8 for the greek theta sign..
return i18n( "Enter coordinates in the following format: \"r; \xCE\xB8°\",\n"
"where r and \xCE\xB8 are the polar coordinates." );
}
TQString PolarCoords::coordinateFormatNoticeMarkup() const
{
// \xCE\xB8 is utf8 for the greek theta sign..
return i18n( "Enter coordinates in the following format: <b>\"r; \xCE\xB8°\"</b>, "
"where r and \xCE\xB8 are the polar coordinates." );
}
Coordinate PolarCoords::toScreen(const TQString& s, bool& ok) const
{
TQRegExp regexp("\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?<EFBFBD>? ?\\)?" );
ok = ( regexp.search( s ) == 0 );
if (ok)
{
TQString rs = regexp.cap( 1 );
double r = KGlobal::locale()->readNumber( rs, &ok );
if ( ! ok ) r = rs.toDouble( &ok );
if ( ! ok ) return Coordinate();
TQString ts = regexp.cap( 2 );
double theta = KGlobal::locale()->readNumber( ts, &ok );
if ( ! ok ) theta = ts.toDouble( &ok );
if ( ! ok ) return Coordinate();
theta *= M_PI;
theta /= 180;
return Coordinate( cos( theta ) * r, sin( theta ) * r );
}
else return Coordinate();
}
void PolarCoords::drawGrid( KigPainter& p, bool showgrid, bool showaxes ) const
{
p.setWholeWinOverlay();
// this instruction in not necessary, but there is a little
// optimization when there are no grid and no axes.
if ( !( showgrid || showaxes ) )
return;
// we multiply by sqrt( 2 ) cause we don't want to miss circles in
// the corners, that intersect with the axes outside of the
// screen..
const double hmax = M_SQRT2*p.window().right();
const double hmin = M_SQRT2*p.window().left();
const double vmax = M_SQRT2*p.window().top();
const double vmin = M_SQRT2*p.window().bottom();
// the intervals:
// we try to have one of them per 40 pixels or so..
const int ntick = static_cast<int>(
kigMax( hmax - hmin, vmax - vmin ) / p.pixelWidth() / 40 ) + 1;
const double hrange = nicenum( hmax - hmin, false );
const double vrange = nicenum( vmax - vmin, false );
const double hd = nicenum( hrange / ( ntick - 1 ), true );
const double vd = nicenum( vrange / ( ntick - 1 ), true );
const double hgraphmin = floor( hmin / hd) * hd;
const double hgraphmax = ceil( hmax / hd ) * hd;
const double vgraphmin = floor( vmin / vd ) * vd;
const double vgraphmax = ceil( vmax / vd ) * vd;
const int hnfrac = kigMax( (int) - floor( log10( hd ) ), 0 );
const int vnfrac = kigMax( (int) - floor( log10( vd ) ), 0 );
const int nfrac = kigMax( hnfrac, vnfrac );
/****** the grid lines ******/
if ( showgrid )
{
double d = kigMin( hd, vd );
double begin = kigMin( kigAbs( hgraphmin ), kigAbs( vgraphmin ) );
if ( kigSgn( hgraphmin ) != kigSgn( hgraphmax ) && kigSgn( vgraphmin ) != kigSgn( vgraphmax ) )
begin = d;
double end = kigMax( hgraphmax, vgraphmax );
// we also want the circles that don't fit entirely in the
// screen..
Coordinate c( 0, 0 );
p.setPen( TQPen( lightGray, 0, DotLine ) );
for ( double i = begin; i <= end + d / 2; i += d )
drawGridLine( p, c, fabs( i ) );
}
/****** the axes ******/
if ( showaxes )
{
p.setPen( TQPen( TQt::gray, 1, TQt::SolidLine ) );
// x axis
p.drawSegment( Coordinate( hmin, 0 ), Coordinate( hmax, 0 ) );
// y axis
p.drawSegment( Coordinate( 0, vmin ), Coordinate( 0, vmax ) );
/****** the numbers ******/
// x axis
for( double i = hgraphmin; i <= hgraphmax + hd / 2; i += hd )
{
// we skip 0 since that would look stupid... (the axes going
// through the 0 etc. )
if( fabs( i ) < 1e-8 ) continue;
TQString is = KGlobal::locale()->formatNumber( fabs( i ), nfrac );
p.drawText(
Rect( Coordinate( i, 0 ), hd, -2*vd ).normalized(),
is, AlignLeft | AlignTop );
};
// y axis...
for ( double i = vgraphmin; i <= vgraphmax + vd / 2; i += vd )
{
if( fabs( i ) < 1e-8 ) continue;
TQString is = KGlobal::locale()->formatNumber( fabs( i ), nfrac );
p.drawText ( Rect( Coordinate( 0, i ), hd, vd ).normalized(),
is, AlignBottom | AlignLeft
);
};
// arrows on the ends of the axes...
p.setPen( TQPen( TQt::gray, 1, TQt::SolidLine ) );
p.setBrush( TQBrush( TQt::gray ) );
std::vector<Coordinate> a;
// the arrow on the right end of the X axis...
a.reserve( 3 );
double u = p.pixelWidth();
a.push_back( Coordinate( hmax - 6 * u, -3 * u) );
a.push_back( Coordinate( hmax, 0 ) );
a.push_back( Coordinate( hmax - 6 * u, 3 * u ) );
// p.drawPolygon( a, true );
p.drawArea( a );
// the arrow on the top end of the Y axis...
a.clear();
a.reserve( 3 );
a.push_back( Coordinate( 3 * u, vmax - 6 * u ) );
a.push_back( Coordinate( 0, vmax ) );
a.push_back( Coordinate( -3 * u, vmax - 6 * u ) );
// p.drawPolygon( a, true );
p.drawArea( a );
}; // if( showaxes )
}
TQValidator* EuclideanCoords::coordinateValidator() const
{
return new CoordinateValidator( false );
}
TQValidator* PolarCoords::coordinateValidator() const
{
return new CoordinateValidator( true );
}
TQStringList CoordinateSystemFactory::names()
{
TQStringList ret;
ret << i18n( "&Euclidean" )
<< i18n( "&Polar" );
return ret;
}
CoordinateSystem* CoordinateSystemFactory::build( int which )
{
if ( which == Euclidean )
return new EuclideanCoords;
else if ( which == Polar )
return new PolarCoords;
else return 0;
}
static const char euclideanTypeString[] = "Euclidean";
static const char polarTypeString[] = "Polar";
CoordinateSystem* CoordinateSystemFactory::build( const char* type )
{
if ( std::string( euclideanTypeString ) == type )
return new EuclideanCoords;
if ( std::string( polarTypeString ) == type )
return new PolarCoords;
else return 0;
}
const char* EuclideanCoords::type() const
{
return euclideanTypeString;
}
const char* PolarCoords::type() const
{
return polarTypeString;
}
int EuclideanCoords::id() const
{
return CoordinateSystemFactory::Euclidean;
}
int PolarCoords::id() const
{
return CoordinateSystemFactory::Polar;
}
TQString CoordinateSystemFactory::setCoordinateSystemStatement( int id )
{
switch( id )
{
case Euclidean:
return i18n( "Set Euclidean Coordinate System" );
case Polar:
return i18n( "Set Polar Coordinate System" );
default:
assert( false );
return TQString();
}
}
Coordinate EuclideanCoords::snapToGrid( const Coordinate& c,
const KigWidget& w ) const
{
Rect rect = w.showingRect();
// we recalc the interval stuff since there is no way to cache it..
// this function is again inspired upon ( public domain ) code from
// the first Graphics Gems book. Credits to Paul S. Heckbert, who
// wrote the "Nice number for graph labels" gem.
const double hmax = rect.right();
const double hmin = rect.left();
const double vmax = rect.top();
const double vmin = rect.bottom();
// the number of intervals we would like to have:
// we try to have one of them per 40 pixels or so..
const int ntick = static_cast<int>(
kigMax( hmax - hmin, vmax - vmin ) / w.pixelWidth() / 40. ) + 1;
const double hrange = nicenum( hmax - hmin, false );
const double vrange = nicenum( vmax - vmin, false );
const double hd = nicenum( hrange / ( ntick - 1 ), true );
const double vd = nicenum( vrange / ( ntick - 1 ), true );
const double hgraphmin = ceil( hmin / hd) * hd;
const double vgraphmin = ceil( vmin / vd ) * vd;
const double nx = tqRound( ( c.x - hgraphmin ) / hd ) * hd + hgraphmin;
const double ny = tqRound( ( c.y - vgraphmin ) / vd ) * vd + vgraphmin;
return Coordinate( nx, ny );
}
Coordinate PolarCoords::snapToGrid( const Coordinate& c,
const KigWidget& w ) const
{
// we reuse the drawGrid code to tqfind
// we multiply by sqrt( 2 ) cause we don't want to miss circles in
// the corners, that intersect with the axes outside of the
// screen..
Rect r = w.showingRect();
const double hmax = M_SQRT2 * r.right();
const double hmin = M_SQRT2 * r.left();
const double vmax = M_SQRT2 * r.top();
const double vmin = M_SQRT2 * r.bottom();
// the intervals:
// we try to have one of them per 40 pixels or so..
const int ntick = static_cast<int>(
kigMax( hmax - hmin, vmax - vmin ) / w.pixelWidth() / 40 ) + 1;
const double hrange = nicenum( hmax - hmin, false );
const double vrange = nicenum( vmax - vmin, false );
const double hd = nicenum( hrange / ( ntick - 1 ), true );
const double vd = nicenum( vrange / ( ntick - 1 ), true );
double d = kigMin( hd, vd );
double dist = c.length();
double ndist = tqRound( dist / d ) * d;
return c.normalize( ndist );
}
void PolarCoords::drawGridLine( KigPainter& p, const Coordinate& c,
double r ) const
{
Rect rect = p.window();
struct iterdata_t
{
int xd;
int yd;
Coordinate ( Rect::*point )() const;
Coordinate ( Rect::*oppositepoint )() const;
double horizAngle;
double vertAngle;
};
static const iterdata_t iterdata[] =
{
{ +1, +1, &Rect::topRight, &Rect::bottomLeft, 0, M_PI/2 },
{ -1, +1, &Rect::topLeft, &Rect::bottomRight, M_PI, M_PI / 2 },
{ -1, -1, &Rect::bottomLeft, &Rect::topRight, M_PI, 3*M_PI/2 },
{ +1, -1, &Rect::bottomRight, &Rect::topLeft, 2*M_PI, 3*M_PI/2 }
};
for ( int i = 0; i < 4; ++i )
{
int xd = iterdata[i].xd;
int yd = iterdata[i].yd;
Coordinate point = ( rect.*iterdata[i].point )();
Coordinate opppoint = ( rect.*iterdata[i].oppositepoint )();
double horizangle = iterdata[i].horizAngle;
double vertangle = iterdata[i].vertAngle;
if ( ( c.x - point.x )*xd > 0 || ( c.y - point.y )*yd > 0 )
continue;
if ( ( c.x - opppoint.x )*-xd > r || ( c.y - opppoint.y )*-yd > r )
continue;
int posdir = xd*yd;
double hd = ( point.x - c.x )*xd;
assert( hd >= 0 );
if ( hd < r )
{
double anglediff = acos( hd/r );
horizangle += posdir * anglediff;
}
hd = ( c.x - opppoint.x )*-xd;
if ( hd >= 0 )
{
double anglediff = asin( hd/r );
vertangle -= posdir * anglediff;
}
double vd = ( point.y - c.y )*yd;
assert( vd >= 0 );
if ( vd < r )
{
double anglediff = acos( vd/r );
vertangle -= posdir * anglediff;
}
vd = ( c.y - opppoint.y ) * -xd;
if ( vd >= 0 )
{
double anglediff = asin( hd/r );
horizangle += posdir * anglediff;
}
p.drawArc( c, r, kigMin( horizangle, vertangle ), kigMax( horizangle, vertangle ) );
}
// p.drawCircle( c, r );
}