/** This file is part of Kig, a KDE program for Interactive Geometry... Copyright (C) 2002 Dominique Devriese 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 #include #include #include #include #include #include #include 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.,+-]+) ?°? ?\\)?" : "\\(? ?([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 ] == '°' ) 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°" ) ); 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( 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 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: \"x;y\", " "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: \"r; \xCE\xB8°\", " "where r and \xCE\xB8 are the polar coordinates." ); } Coordinate PolarCoords::toScreen(const TQString& s, bool& ok) const { TQRegExp regexp("\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?°? ?\\)?" ); 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( 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 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( 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( 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 ); }