diff options
Diffstat (limited to 'kig/misc')
44 files changed, 14589 insertions, 0 deletions
diff --git a/kig/misc/Makefile.am b/kig/misc/Makefile.am new file mode 100644 index 00000000..d97d7989 --- /dev/null +++ b/kig/misc/Makefile.am @@ -0,0 +1,50 @@ +INCLUDES=$(all_includes) +noinst_LTLIBRARIES=libmisc.la +noinst_HEADERS = \ + argsparser.h \ + builtin_stuff.h \ + calcpaths.h \ + common.h \ + conic-common.h \ + coordinate.h \ + coordinate_system.h \ + cubic-common.h \ + goniometry.h \ + guiaction.h \ + kigfiledialog.h \ + kiginputdialog.h \ + kignumerics.h \ + kigpainter.h \ + kigtransform.h \ + lists.h \ + object_constructor.h \ + object_hierarchy.h \ + rect.h \ + screeninfo.h \ + special_constructors.h + +libmisc_la_SOURCES = \ + argsparser.cpp \ + builtin_stuff.cc \ + calcpaths.cc \ + common.cpp \ + conic-common.cpp \ + coordinate.cpp \ + coordinate_system.cpp \ + cubic-common.cc \ + goniometry.cc \ + guiaction.cc \ + kigfiledialog.cc \ + kiginputdialog.cc \ + kignumerics.cpp \ + kigpainter.cpp \ + kigtransform.cpp \ + lists.cc \ + object_constructor.cc \ + object_hierarchy.cc \ + rect.cc \ + screeninfo.cc \ + special_constructors.cc + +libmisc_la_LIBADD=-lm +METASOURCES=AUTO diff --git a/kig/misc/argsparser.cpp b/kig/misc/argsparser.cpp new file mode 100644 index 00000000..c2387970 --- /dev/null +++ b/kig/misc/argsparser.cpp @@ -0,0 +1,267 @@ +// 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 "argsparser.h" + +#include "../objects/object_imp.h" +#include "../objects/object_holder.h" + +#include <cassert> +#include <algorithm> +#include <kdebug.h> + +void ArgsParser::initialize( const struct spec* args, int n ) +{ + std::vector<spec> vect( args, args + n ); + initialize( vect ); +} + +ArgsParser::ArgsParser() +{ +} + +ArgsParser::ArgsParser( const std::vector<spec>& args ) +{ + initialize( args ); +} + +void ArgsParser::initialize( const std::vector<spec>& args ) +{ + margs = args; +} + +ArgsParser::ArgsParser( const spec* args, int n ) +{ + initialize( args, n ); +} + +static bool hasimp( const ObjectCalcer& o, const ObjectImpType* imptype ) +{ + return o.imp()->inherits( imptype ); +} + +static bool hasimp( const ObjectImp& o, const ObjectImpType* imptype ) +{ + return o.inherits( imptype ); +} + +static bool isvalid( const ObjectImp& o ) +{ + return o.valid(); +} + +static bool isvalid( const ObjectCalcer& o ) +{ + return o.imp()->valid(); +} + +// we use a template method that is used for both Objects and Args to +// not have to write the same thing twice.. +template <class Collection> +static int check( const Collection& c, const std::vector<ArgsParser::spec>& margs ) +{ + std::vector<bool> found( margs.size() ); + + for ( typename Collection::const_iterator o = c.begin(); o != c.end(); ++o ) + { + for ( uint i = 0; i < margs.size(); ++i ) + { + if ( hasimp( **o, margs[i].type ) && !found[i] ) + { + // object o is of a type that we're looking for + found[i] = true; + goto matched; + }; + }; + return ArgsParser::Invalid; + matched: + ; + }; + for( uint i = 0; i < margs.size(); ++i ) + if ( !found[i] ) return ArgsParser::Valid; + return ArgsParser::Complete; +} + +int ArgsParser::check( const Args& os ) const +{ + return ::check( os, margs ); +} + +int ArgsParser::check( const std::vector<ObjectCalcer*>& os ) const +{ + return ::check( os, margs ); +} + +template <typename Collection> +static Collection parse( const Collection& os, + const std::vector<ArgsParser::spec> margs ) +{ + Collection ret( margs.size(), static_cast<typename Collection::value_type>( 0 ) ); + + for ( typename Collection::const_iterator o = os.begin(); o != os.end(); ++o ) + { + for( uint i = 0; i < margs.size(); ++i ) + if ( hasimp( **o, margs[i].type ) && ret[i] == 0 ) + { + // object o is of a type that we're looking for + ret[i] = *o; + goto added; + } + added: + ; + }; + // remove 0's from the output.. + ret.erase( + std::remove( ret.begin(), ret.end(), + static_cast<typename Collection::value_type>( 0 ) ), + ret.end() ); + return ret; +} + +Args ArgsParser::parse( const Args& os ) const +{ + return ::parse( os, margs ); +} + +std::vector<ObjectCalcer*> ArgsParser::parse( const std::vector<ObjectCalcer*>& os ) const +{ + return ::parse( os, margs ); +} + +ArgsParser ArgsParser::without( const ObjectImpType* type ) const +{ + std::vector<spec> ret; + ret.reserve( margs.size() - 1 ); + for ( uint i = 0; i < margs.size(); ++i ) + if ( margs[i].type != type ) + ret.push_back( margs[i] ); + return ArgsParser( ret ); +} + +ArgsParser::spec ArgsParser::findSpec( const ObjectImp* obj, const Args& parents ) const +{ + spec ret; + ret.type = 0; + + std::vector<bool> found( margs.size(), false ); + + for ( Args::const_iterator o = parents.begin(); + o != parents.end(); ++o ) + { + for ( uint i = 0; i < margs.size(); ++i ) + { + if ( (*o)->inherits( margs[i].type ) && !found[i] ) + { + // object o is of a type that we're looking for + found[i] = true; + if ( *o == obj ) return margs[i]; + // i know that "goto's are *evil*", but they're very useful + // and completely harmless if you use them as better "break;" + // statements.. trust me ;) + goto matched; + }; + }; + matched: + ; + }; + kdDebug() << k_funcinfo << "no proper spec found :(" << endl; + return ret; +} + +const ObjectImpType* ArgsParser::impRequirement( + const ObjectImp* o, const Args& parents ) const +{ + spec s = findSpec( o, parents ); + return s.type; +} + +std::string ArgsParser::usetext( const ObjectImp* obj, const Args& sel ) const +{ + spec s = findSpec( obj, sel ); + return s.usetext; +} + +template<typename Collection> +static bool checkArgs( const Collection& os, uint min, const std::vector<ArgsParser::spec>& argsspec ) +{ + assert( os.size() <= argsspec.size() ); + if( os.size() < min ) return false; + uint checknum = os.size(); + for ( uint i = 0; i < checknum; ++i ) + { + if( !isvalid( *os[i] ) ) return false; + if( !hasimp( *os[i], argsspec[i].type ) ) return false; + } + return true; +} + +bool ArgsParser::checkArgs( const Args& os ) const +{ + return checkArgs( os, margs.size() ); +} + +bool ArgsParser::checkArgs( const Args& os, uint min ) const +{ + return ::checkArgs( os, min, margs ); +} + +bool ArgsParser::checkArgs( const std::vector<ObjectCalcer*>& os ) const +{ + return checkArgs( os, margs.size() ); +} + +bool ArgsParser::checkArgs( const std::vector<ObjectCalcer*>& os, uint minobjects ) const +{ + return ::checkArgs( os, minobjects, margs ); +} + +ArgsParser::~ArgsParser() +{ +} + +bool ArgsParser::isDefinedOnOrThrough( const ObjectImp* o, const Args& parents ) const +{ + spec s = findSpec( o, parents ); + return s.onOrThrough; +} + +std::string ArgsParser::selectStatement( const Args& selection ) const +{ + std::vector<bool> found( margs.size(), false ); + + for ( Args::const_iterator o = selection.begin(); + o != selection.end(); ++o ) + { + for ( uint i = 0; i < margs.size(); ++i ) + { + if ( (*o)->inherits( margs[i].type ) && !found[i] ) + { + // object o is of a type that we're looking for + found[i] = true; + break; + } + } + } + for ( uint i = 0; i < margs.size(); ++i ) + { + if ( !found[i] ) + return margs[i].selectstat; + } + kdDebug() << k_funcinfo << "no proper select statement found :(" << endl; + return 0; +} + diff --git a/kig/misc/argsparser.h b/kig/misc/argsparser.h new file mode 100644 index 00000000..001d9359 --- /dev/null +++ b/kig/misc/argsparser.h @@ -0,0 +1,187 @@ +// 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. + +#ifndef KIG_MISC_ARGSPARSER_H +#define KIG_MISC_ARGSPARSER_H + +#include "../objects/common.h" + +#include <string> + +class ObjectImpType; + +/** + * This class is meant to take care of checking the types of the + * parents to ObjectCalcer's, and to put them in the correct order. + * An ObjectType should construct an ArgsParser with a specification + * of the arguments it wants. This specification is given as an array + * of ArgsParser::spec structs. This struct contains a pointer to an + * ObjectImpType ( which is the type you want the argument to have ), + * a string ( which is an I18N_NOOP'd string describing what you will + * be using the argument for ) and a boolean ( which says whether the + * constructed object is by construction on the curve argument ( if + * the constructed object is a point ), or whether the constructed + * object is by construction through the point argument ( if the + * constructed object is a curve ) ). + * + * An ObjectType using an ArgsParser to take care of the various + * things that it can handle ( impRequirement, the sortArgs functions + * and the isDefinedOnOrThrough stuff ), should inherit from + * ArgsParserObjectType, which takes care of calling the ArgsParser + * for these things... It also allows you to use a convenient + * ObjectConstructor for your type. + * + * E.g., let's see what CircleBCPType has for its arguments spec: + * here's some code: + * \code + * static const ArgsParser::spec argsspecTranslation[] = + * { + * { ObjectImp::stype(), I18N_NOOP("Translate this object"), false }, + * { VectorImp::stype(), I18N_NOOP("Translate by this vector"), false } + * }; + * + * TranslatedType::TranslatedType() + * : ArgsParserObjectType( "Translation", argsspecTranslation, 2 ) + * { + * } + * + * ObjectImp* TranslatedType::calc( const Args& args, const KigDocument& ) const + * { + * if ( ! margsparser.checkArgs( args ) ) return new InvalidImp; + * + * Coordinate dir = static_cast<const VectorImp*>( args[1] )->dir(); + * Transformation t = Transformation::translation( dir ); + * + * return args[0]->transform( t ); + * } + * \endcode + * + * As you can see above, the argsspec can be declared right in the + * cpp-file. The usetexts explain to the user what the argument in + * question will be used for. The boolean says that in this case, the + * constructed object is not by construction on or through one of its + * arguments. In the constructor, you simply call the + * ArgsParserObjectType with the argsspec struct you defined, and the + * number of arguments in the argsspec ( in this case 2 ). + * + * In the calc function, you can rely on the arguments already being + * in the correct order ( the same order as you put them in in the + * arguments spec. You should use the checkArgs function to check if + * all the arguments are valid, and if they aren't return a + * InvalidImp. All objects can always become invalid ( e.g. an + * intersection point of two non-intersecting conics can become valid + * again when the conics move ), and you should always check for this. + * + * An interesting to note here is that the first argument is of a more + * general type than the second. A VectorImp is *also* an ObjectImp. + * In general, when this happens, you should put the more general type + * first, as in general this produces the results that the user + * expects. I have no formal proof for this, just talking from + * experience. It might be that you experience different things, but + * unless you're sure of the results, put the more general type first. + * + * This class uses a pretty basic algorithm for doing the parsing ( + * e.g. if a match fails in one order, it does not try a different + * order, which could perhaps be necessary in the case of having more + * general argument types in the same argument spec ). However, the + * current algorithm works in all the situation where I've tested it, + * and I don't feel the need to change it. Feel free to do so if you + * like, but even if you do, I'm not sure if I will include it in + * mainline Kig. + */ +class ArgsParser +{ +public: + /** + * this are some enum values that we return from some functions. + */ + enum { Invalid = 0, Valid = 1, Complete = 2 }; + struct spec { const ObjectImpType* type; std::string usetext; std::string selectstat; bool onOrThrough;}; +private: + /** + * the args spec.. + */ + std::vector<spec> margs; + + spec findSpec( const ObjectImp* o, const Args& parents ) const; +public: + ArgsParser( const struct spec* args, int n ); + ArgsParser( const std::vector<spec>& args ); + ArgsParser(); + ~ArgsParser(); + + void initialize( const std::vector<spec>& args ); + void initialize( const struct spec* args, int n ); + + /** + * returns a new ArgsParser that wants the same args, except for the + * ones of the given type.. + */ + ArgsParser without( const ObjectImpType* type ) const; + // checks if os matches the argument list this parser should parse.. + int check( const Args& os ) const; + int check( const std::vector<ObjectCalcer*>& os ) const; + /** + * returns the usetext for the argument that o would be used for, + * if sel were used as parents.. + * \p o should be in \p sel ... + */ + std::string usetext( const ObjectImp* o, const Args& sel ) const; + + /** + * returns the select statement for the next selectable argument + * when the given args are selected. + */ + std::string selectStatement( const Args& sel ) const; + + // this reorders the objects or args so that they are in the same + // order as the requested arguments.. + Args parse( const Args& os ) const; + std::vector<ObjectCalcer*> parse( const std::vector<ObjectCalcer*>& os ) const; + + /** + * returns the minimal ObjectImp ID that \p o needs to inherit in order + * to be useful.. \p o should be part of \p parents . + */ + const ObjectImpType* impRequirement( const ObjectImp* o, const Args& parents ) const; + + /** + * Supposing that \p parents would be given as parents, this function + * returns whether the returned ObjectImp will be, by construction, + * on \p o ( if \p o is a curve ), or through \p o ( if \p o is a point ). + */ + bool isDefinedOnOrThrough( const ObjectImp* o, const Args& parents ) const; + + // Checks the args according to this args specification. If the + // objects should never have occurred, then an assertion failure + // will happen, if one of the args is invalid, then false will be + // returned, if all is fine, then true is returned.. + // assert that the objects are of the right types, and in the right + // order as what would be returned by parse( os ).. If minobjects + // is provided, then not all objects are needed, and it is enough if + // at least minobjects are available.. Use this for object types + // that can calc a temporary example object using less than the + // required args. These args need to be at the end of argsspec + + // anyobjsspec. If minobjects is not provided, then it is assumed + // that all args are necessary. + bool checkArgs( const std::vector<ObjectCalcer*>& os ) const; + bool checkArgs( const std::vector<ObjectCalcer*>& os, uint minobjects ) const; + bool checkArgs( const Args& os ) const; + bool checkArgs( const Args& os, uint minobjects ) const; +}; + +#endif diff --git a/kig/misc/boost_intrusive_pointer.hpp b/kig/misc/boost_intrusive_pointer.hpp new file mode 100644 index 00000000..a278e736 --- /dev/null +++ b/kig/misc/boost_intrusive_pointer.hpp @@ -0,0 +1,256 @@ +// Copyright (C) 2003 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. + + + +// This code comes from the boost::intrusive_ptr. I adapted it to +// suit my needs ( no dependencies on other boost libs, change the +// namespace to avoid conflicts, + +#ifndef MYBOOST_INTRUSIVE_PTR_HPP_INCLUDED +#define MYBOOST_INTRUSIVE_PTR_HPP_INCLUDED + +// +// intrusive_ptr.hpp +// +// Copyright (c) 2001, 2002 Peter Dimov +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// See http://www.boost.org/libs/smart_ptr/intrusive_ptr.html for documentation. +// + +#include <functional> // for std::less +#include <iosfwd> // for std::basic_ostream + + +namespace myboost +{ + +// +// intrusive_ptr +// +// A smart pointer that uses intrusive reference counting. +// +// Relies on unqualified calls to +// +// void intrusive_ptr_add_ref(T * p); +// void intrusive_ptr_release(T * p); +// +// (p != 0) +// +// The object is responsible for destroying itself. +// + +template<class T> class intrusive_ptr +{ +private: + + typedef intrusive_ptr this_type; + +public: + + typedef T element_type; + + intrusive_ptr(): p_(0) + { + } + + intrusive_ptr(T * p, bool add_ref = true): p_(p) + { + if(p_ != 0 && add_ref) intrusive_ptr_add_ref(p_); + } + +#if !defined(BOOST_NO_MEMBER_TEMPLATES) || defined(BOOST_MSVC6_MEMBER_TEMPLATES) + + template<class U> intrusive_ptr(intrusive_ptr<U> const & rhs): p_(rhs.get()) + { + if(p_ != 0) intrusive_ptr_add_ref(p_); + } + +#endif + + intrusive_ptr(intrusive_ptr const & rhs): p_(rhs.p_) + { + if(p_ != 0) intrusive_ptr_add_ref(p_); + } + + ~intrusive_ptr() + { + if(p_ != 0) intrusive_ptr_release(p_); + } + +#if !defined(BOOST_NO_MEMBER_TEMPLATES) || defined(BOOST_MSVC6_MEMBER_TEMPLATES) + + template<class U> intrusive_ptr & operator=(intrusive_ptr<U> const & rhs) + { + this_type(rhs).swap(*this); + return *this; + } + +#endif + + intrusive_ptr & operator=(intrusive_ptr const & rhs) + { + this_type(rhs).swap(*this); + return *this; + } + + intrusive_ptr & operator=(T * rhs) + { + this_type(rhs).swap(*this); + return *this; + } + + T * get() const + { + return p_; + } + + T & operator*() const + { + return *p_; + } + + T * operator->() const + { + return p_; + } + + typedef T * (intrusive_ptr::*unspecified_bool_type) () const; + + operator unspecified_bool_type () const + { + return p_ == 0? 0: &intrusive_ptr::get; + } + + // operator! is a Borland-specific workaround + bool operator! () const + { + return p_ == 0; + } + + void swap(intrusive_ptr & rhs) + { + T * tmp = p_; + p_ = rhs.p_; + rhs.p_ = tmp; + } + +private: + + T * p_; +}; + +template<class T, class U> inline bool operator==(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b) +{ + return a.get() == b.get(); +} + +template<class T, class U> inline bool operator!=(intrusive_ptr<T> const & a, intrusive_ptr<U> const & b) +{ + return a.get() != b.get(); +} + +template<class T> inline bool operator==(intrusive_ptr<T> const & a, T * b) +{ + return a.get() == b; +} + +template<class T> inline bool operator!=(intrusive_ptr<T> const & a, T * b) +{ + return a.get() != b; +} + +template<class T> inline bool operator==(T * a, intrusive_ptr<T> const & b) +{ + return a == b.get(); +} + +template<class T> inline bool operator!=(T * a, intrusive_ptr<T> const & b) +{ + return a != b.get(); +} + +#if __GNUC__ == 2 && __GNUC_MINOR__ <= 96 + +// Resolve the ambiguity between our op!= and the one in rel_ops + +template<class T> inline bool operator!=(intrusive_ptr<T> const & a, intrusive_ptr<T> const & b) +{ + return a.get() != b.get(); +} + +#endif + +template<class T> inline bool operator<(intrusive_ptr<T> const & a, intrusive_ptr<T> const & b) +{ + return std::less<T *>()(a.get(), b.get()); +} + +template<class T> void swap(intrusive_ptr<T> & lhs, intrusive_ptr<T> & rhs) +{ + lhs.swap(rhs); +} + +// mem_fn support + +template<class T> T * get_pointer(intrusive_ptr<T> const & p) +{ + return p.get(); +} + +template<class T, class U> intrusive_ptr<T> static_pointer_cast(intrusive_ptr<U> const & p) +{ + return static_cast<T *>(p.get()); +} + +template<class T, class U> intrusive_ptr<T> dynamic_pointer_cast(intrusive_ptr<U> const & p) +{ + return dynamic_cast<T *>(p.get()); +} + +// operator<< + +#if defined(__GNUC__) && (__GNUC__ < 3) + +template<class Y> std::ostream & operator<< (std::ostream & os, intrusive_ptr<Y> const & p) +{ + os << p.get(); + return os; +} + +#else + +template<class E, class T, class Y> std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, intrusive_ptr<Y> const & p) +{ + os << p.get(); + return os; +} + +#endif + +} // namespace myboost + +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + +#endif // #ifndef MYBOOST_INTRUSIVE_PTR_HPP_INCLUDED diff --git a/kig/misc/builtin_stuff.cc b/kig/misc/builtin_stuff.cc new file mode 100644 index 00000000..e162e7ac --- /dev/null +++ b/kig/misc/builtin_stuff.cc @@ -0,0 +1,596 @@ +// Copyright (C) 2003 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 "builtin_stuff.h" + +#include <config.h> + +#include "object_constructor.h" +#include "lists.h" +#include "special_constructors.h" +#include "guiaction.h" + +#include "../objects/angle_type.h" +#include "../objects/arc_type.h" +#include "../objects/circle_type.h" +#include "../objects/conic_types.h" +#include "../objects/cubic_type.h" +#include "../objects/intersection_types.h" +#include "../objects/inversion_type.h" +#include "../objects/line_imp.h" +#include "../objects/line_type.h" +#include "../objects/object_imp.h" +#include "../objects/other_imp.h" +#include "../objects/other_type.h" +#include "../objects/point_type.h" +#include "../objects/tests_type.h" +#include "../objects/transform_types.h" +#include "../objects/vector_type.h" +#include "../objects/polygon_type.h" + +#include <klocale.h> + +void setupBuiltinStuff() +{ + static bool done = false; + if ( ! done ) + { + ObjectConstructorList* ctors = ObjectConstructorList::instance(); + GUIActionList* actions = GUIActionList::instance(); + ObjectConstructor* c = 0; + + // segment... + c = new SimpleObjectTypeConstructor( + SegmentABType::instance(), I18N_NOOP( "Segment" ), + I18N_NOOP( "A segment constructed from its start and end point" ), + "segment" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_segment", Qt::Key_S ) ); + + // line by two points.. + c = new SimpleObjectTypeConstructor( + LineABType::instance(), I18N_NOOP( "Line by Two Points" ), + I18N_NOOP( "A line constructed through two points"), "line" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_linettp", Qt::Key_L ) ); + + // ray by two points.. + c = new SimpleObjectTypeConstructor( + RayABType::instance(), I18N_NOOP( "Half-Line" ), + I18N_NOOP( "A half-line by its start point, and another point somewhere on it." ), + "ray" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_ray", Qt::Key_R ) ); + + // perpendicular line + c = new SimpleObjectTypeConstructor( + LinePerpendLPType::instance(), I18N_NOOP( "Perpendicular" ), + I18N_NOOP( "A line constructed through a point, perpendicular to another line or segment." ), + "perpendicular" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_lineperpend" ) ); + + // parallel line + c = new SimpleObjectTypeConstructor( + LineParallelLPType::instance(), I18N_NOOP( "Parallel" ), + I18N_NOOP( "A line constructed through a point, and parallel to another line or segment" ), + "parallel" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_lineparallel" ) ); + + // circle + c = new SimpleObjectTypeConstructor( + CircleBCPType::instance(), I18N_NOOP( "Circle by Center && Point" ), + I18N_NOOP( "A circle constructed by its center and a point that pertains to it" ), + "circlebcp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_circlebcp", Qt::Key_C ) ); + + c = new SimpleObjectTypeConstructor( + CircleBTPType::instance(), I18N_NOOP( "Circle by Three Points" ), + I18N_NOOP( "A circle constructed through three points" ), + "circlebtp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_circlebtp" ) ); + + // declare this object static to this function, so it gets deleted + // at the end of the program, without us having to wonder about + // deleting it.. We don't want to register this + // object-constructor, because that way, "construct the bisector" + // would appear twice in the angle popup menu: once as the generic + // construct a property stuff, and once because of this ctor.. + // we only register the guiaction, cause it makes sense to have a + // toolbar icon for this.. + static PropertyObjectConstructor anglebisectionctor( + AngleImp::stype(), + I18N_NOOP( "Construct Bisector of This Angle" ), + I18N_NOOP( "Select the angle you want to construct the bisector of..." ), + I18N_NOOP( "Angle Bisector" ), + I18N_NOOP( "The bisector of an angle" ), + "angle_bisector", + "angle-bisector" ); + actions->add( new ConstructibleAction( &anglebisectionctor, "objects_new_angle_bisector" ) ); + + // conic stuff + c = new SimpleObjectTypeConstructor( + ConicB5PType::instance(), I18N_NOOP( "Conic by Five Points" ), + I18N_NOOP( "A conic constructed through five points" ), + "conicb5p" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_conicb5p" ) ); + + c = new SimpleObjectTypeConstructor( + ConicBAAPType::instance(), + I18N_NOOP( "Hyperbola by Asymptotes && Point" ), + I18N_NOOP( "A hyperbola with given asymptotes through a point" ), + "conicbaap" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_conicbaap" ) ); + + c = new SimpleObjectTypeConstructor( + EllipseBFFPType::instance(), + I18N_NOOP( "Ellipse by Focuses && Point" ), // focuses is used in preference to foci + I18N_NOOP( "An ellipse constructed by its focuses and a point that pertains to it" ), + "ellipsebffp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_ellipsebffp" ) ); + + c = new SimpleObjectTypeConstructor( + HyperbolaBFFPType::instance(), + I18N_NOOP( "Hyperbola by Focuses && Point" ), // focuses is used in preference to foci + I18N_NOOP( "A hyperbola constructed by its focuses and a point that pertains to it" ), + "hyperbolabffp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_hyperbolabffp" ) ); + + c = new SimpleObjectTypeConstructor( + ConicBDFPType::instance(), + I18N_NOOP( "Conic by Directrix, Focus && Point" ), + I18N_NOOP( "A conic with given directrix and focus, through a point" ), + "conicbdfp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_conicbdfp" ) ); + + c = new SimpleObjectTypeConstructor( + ParabolaBTPType::instance(), + I18N_NOOP( "Vertical Parabola by Three Points" ), + I18N_NOOP( "A vertical parabola constructed through three points" ), + "parabolabtp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_parabolabtp" ) ); + + c = new SimpleObjectTypeConstructor( + CubicB9PType::instance(), + I18N_NOOP( "Cubic Curve by Nine Points" ), + I18N_NOOP( "A cubic curve constructed through nine points" ), + "cubicb9p" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_cubicb9p" ) ); + + c = new SimpleObjectTypeConstructor( + ConicPolarPointType::instance(), + I18N_NOOP( "Polar Point of a Line" ), + I18N_NOOP( "The polar point of a line with respect to a conic." ), + "polarpoint" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_pointpolar" ) ); + + c = new SimpleObjectTypeConstructor( + ConicPolarLineType::instance(), + I18N_NOOP( "Polar Line of a Point" ), + I18N_NOOP( "The polar line of a point with respect to a conic." ), + "polarline" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_linepolar" ) ); + + c = new SimpleObjectTypeConstructor( + CubicNodeB6PType::instance(), + I18N_NOOP( "Cubic Curve with Node by Six Points" ), + I18N_NOOP( "A cubic curve with a nodal point at the origin through six points" ), + "cubicnodeb6p" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_cubicnodeb6p" ) ); + + c = new SimpleObjectTypeConstructor( + CubicCuspB4PType::instance(), + I18N_NOOP( "Cubic Curve with Cusp by Four Points" ), + I18N_NOOP( "A cubic curve with a horizontal cusp at the origin through four points" ), + "cubiccuspb4p" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_cubiccuspb4p" ) ); + + c = new SimpleObjectTypeConstructor( + ConicDirectrixType::instance(), + I18N_NOOP( "Directrix of a Conic" ), + I18N_NOOP( "The directrix line of a conic." ), + "directrix" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_linedirectrix" ) ); + + c = new SimpleObjectTypeConstructor( + AngleType::instance(), + I18N_NOOP( "Angle by Three Points" ), + I18N_NOOP( "An angle defined by three points" ), + "angle" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_angle", Qt::Key_A ) ); + + c = new SimpleObjectTypeConstructor( + EquilateralHyperbolaB4PType::instance(), + I18N_NOOP( "Equilateral Hyperbola by Four Points" ), + I18N_NOOP( "An equilateral hyperbola constructed through four points" ), + "equilateralhyperbolab4p" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_equilateralhyperbolab4p" ) ); + + { + // now for the Mid Point action. It does both the mid point of + // a segment, and the mid point of two points. The midpoint of + // two segments just shows the mid point property, and therefore + // doesn't need to be added to the ctors, because there are + // already facilities to construct an object's properties.. + // therefore, we add only an mpotp to the ctors, and add the + // merged constructor only to the actions.. + ctors->add( new MidPointOfTwoPointsConstructor() ); + + ObjectConstructor* mpotp = new MidPointOfTwoPointsConstructor(); + ObjectConstructor* mpos = new PropertyObjectConstructor( + SegmentImp::stype(), I18N_NOOP( "Construct the midpoint of this segment" ), + "", "", "", "", "mid-point" ); + + // make this a static object, so it gets deleted at the end of + // the program. + static MergeObjectConstructor m( + I18N_NOOP( "Mid Point" ), + I18N_NOOP( "The midpoint of a segment or two other points" ), + "bisection" ); + m.merge( mpotp ); + m.merge( mpos ); + actions->add( new ConstructibleAction( &m, "objects_new_midpoint", Qt::Key_M ) ); + }; + + c = new SimpleObjectTypeConstructor( + VectorType::instance(), + I18N_NOOP( "Vector" ), + I18N_NOOP( "Construct a vector from two given points." ), + "vector" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_vector", Qt::Key_V ) ); + + c = new SimpleObjectTypeConstructor( + VectorSumType::instance(), + I18N_NOOP( "Vector Sum" ), + I18N_NOOP( "Construct the vector sum of two vectors." ), + "vectorsum" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_vectorsum", 0 ) ); + + c = new SimpleObjectTypeConstructor( + LineByVectorType::instance(), + I18N_NOOP( "Line by Vector" ), + I18N_NOOP( "Construct the line by a given vector though a given point." ), + "linebyvector" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_linebyvector", 0 ) ); + + c = new SimpleObjectTypeConstructor( + HalflineByVectorType::instance(), + I18N_NOOP( "Half-Line by Vector" ), + I18N_NOOP( "Construct the half-line by a given vector starting at given point." ), + "halflinebyvector" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_halflinebyvector", 0 ) ); + + c = new SimpleObjectTypeConstructor( + ArcBTPType::instance(), + I18N_NOOP( "Arc by Three Points" ), + I18N_NOOP( "Construct an arc through three points." ), + "arc" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_arcbtp" ) ); + + c = new SimpleObjectTypeConstructor( + ArcBCPAType::instance(), + I18N_NOOP( "Arc by Center, Angle && Point" ), + I18N_NOOP( "Construct an arc by its center and a given angle, " + "starting at a given point" ), + "arcbcpa" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_arcbcpa" ) ); + + c = new SimpleObjectTypeConstructor( + ParabolaBDPType::instance(), + I18N_NOOP( "Parabola by Directrix && Focus" ), + I18N_NOOP( "A parabola defined by its directrix and focus" ), + "parabolabdp" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_parabolabdp" ) ); + + // Transformation stuff.. + c = new InversionConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_inversion" ) ); + + c = new SimpleObjectTypeConstructor( + TranslatedType::instance(), + I18N_NOOP( "Translate" ), + I18N_NOOP( "The translation of an object by a vector" ), + "translation" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_translation" ) ); + + c = new SimpleObjectTypeConstructor( + PointReflectionType::instance(), + I18N_NOOP( "Reflect in Point" ), + I18N_NOOP( "An object reflected in a point" ), + "centralsymmetry" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_pointreflection" ) ); + + c = new SimpleObjectTypeConstructor( + LineReflectionType::instance(), + I18N_NOOP( "Reflect in Line" ), + I18N_NOOP( "An object reflected in a line" ), + "mirrorpoint" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_linereflection" ) ); + + c = new SimpleObjectTypeConstructor( + RotationType::instance(), + I18N_NOOP( "Rotate" ), + I18N_NOOP( "An object rotated by an angle around a point" ), + "rotation" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_rotation" ) ); + + c = new SimpleObjectTypeConstructor( + ScalingOverCenterType::instance(), + I18N_NOOP( "Scale" ), + I18N_NOOP( "Scale an object over a point, by the ratio given by the length of a segment" ), + "scale" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_scalingovercenter" ) ); + + c = new SimpleObjectTypeConstructor( + ScalingOverLineType::instance(), + I18N_NOOP( "Scale over Line" ), + I18N_NOOP( "An object scaled over a line, by the ratio given by the length of a segment" ), + "stretch" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_scalingoverline" ) ); + + c = new SimpleObjectTypeConstructor( + ScalingOverCenter2Type::instance(), + I18N_NOOP( "Scale (ratio given by two segments)" ), + I18N_NOOP( "Scale an object over a point, by the ratio given by the length of two segments" ), + "scale" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_scalingovercenter2" ) ); + + c = new SimpleObjectTypeConstructor( + ScalingOverLine2Type::instance(), + I18N_NOOP( "Scale over Line (ratio given by two segments)" ), + I18N_NOOP( "An object scaled over a line, by the ratio given by the length of two segments" ), + "stretch" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_scalingoverline2" ) ); + + c = new SimpleObjectTypeConstructor( + SimilitudeType::instance(), + I18N_NOOP( "Apply Similitude" ), + I18N_NOOP( "Apply a similitude to an object ( the sequence of a scaling and rotation around a center )" ), + "similitude" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_similitude" ) ); + + c = new SimpleObjectTypeConstructor( + HarmonicHomologyType::instance(), + I18N_NOOP( "Harmonic Homology" ), + I18N_NOOP( "The harmonic homology with a given center and a given axis (this is a projective transformation)" ), + "harmonichomology" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_harmonichomology" ) ); + + c = new GenericAffinityConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_genericaffinity" ) ); + + c = new GenericProjectivityConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_genericprojectivity" ) ); + + c = new SimpleObjectTypeConstructor( + CastShadowType::instance(), + I18N_NOOP( "Draw Projective Shadow" ), + I18N_NOOP( "The shadow of an object with a given light source and projection plane (indicated by a line)" ), + "castshadow" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_castshadow" ) ); + +// c = new SimpleObjectTypeConstructor( +// ProjectiveRotationType::instance(), +// I18N_NOOP( "Rotate Projectively" ), +// I18N_NOOP( "An object projectively rotated by an angle and a half-line" ), +// "projectiverotation" ); +// ctors->add( c ); +// actions->add( new ConstructibleAction( c, "objects_new_projectiverotation" ) ); + + c = new MultiObjectTypeConstructor( + ConicAsymptoteType::instance(), + I18N_NOOP( "Asymptotes of a Hyperbola" ), + I18N_NOOP( "The two asymptotes of a hyperbola." ), + "conicasymptotes", -1, 1 ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_lineconicasymptotes" ) ); + + c = new ConicRadicalConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_lineconicradical") ); + + /* ----------- start polygons --------- */ + + c = new SimpleObjectTypeConstructor( + TriangleB3PType::instance(), + I18N_NOOP( "Triangle by Its Vertices" ), + I18N_NOOP( "Construct a triangle given its three vertices." ), + "triangle" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_trianglebtp" ) ); + + c = new PolygonBNPTypeConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_polygonbnp" )); + + c = new PolygonBCVConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_polygonbcv" ) ); + + c = new PolygonVertexTypeConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_polygonvertices" )); + + c = new PolygonSideTypeConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_polygonsides" )); + + c = new SimpleObjectTypeConstructor( + ConvexHullType::instance(), I18N_NOOP( "Convex Hull" ), + I18N_NOOP( "A polygon that corresponds to the convex hull of another polygon" ), + "convexhull" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_convexhull" ) ); + + /* ----------- end polygons --------- */ + + c = new LocusConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_locus" ) ); + + // tests + c = new TestConstructor( + AreParallelType::instance(), + I18N_NOOP( "Parallel Test" ), + I18N_NOOP( "Test whether two given lines are parallel" ), + "testparallel" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_areparallel" ) ); + + c = new TestConstructor( + AreOrthogonalType::instance(), + I18N_NOOP( "Orthogonal Test" ), + I18N_NOOP( "Test whether two given lines are orthogonal" ), + "testorthogonal" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_areorthogonal" ) ); + + c = new TestConstructor( + AreCollinearType::instance(), + I18N_NOOP( "Collinear Test" ), + I18N_NOOP( "Test whether three given points are collinear" ), + "testcollinear" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_arecollinear" ) ); + + c = new TestConstructor( + ContainsTestType::instance(), + I18N_NOOP( "Contains Test" ), + I18N_NOOP( "Test whether a given curve contains a given point" ), + "testcontains" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_containstest" ) ); + + c = new TestConstructor( + InPolygonTestType::instance(), + I18N_NOOP( "In Polygon Test" ), + I18N_NOOP( "Test whether a given polygon contains a given point" ), + "test" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_inpolygontest" ) ); + + c = new TestConstructor( + ConvexPolygonTestType::instance(), + I18N_NOOP( "Convex Polygon Test" ), + I18N_NOOP( "Test whether a given polygon is convex" ), + "test" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_convexpolygontest" ) ); + + c = new TestConstructor( + SameDistanceType::instance(), + I18N_NOOP( "Distance Test" ), + I18N_NOOP( "Test whether a given point have the same distance from a given point " + "and from another given point" ), + "testdistance" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_distancetest" ) ); + + c = new TestConstructor( + VectorEqualityTestType::instance(), + I18N_NOOP( "Vector Equality Test" ), + I18N_NOOP( "Test whether two vectors are equal" ), + "test" ); +// "testequal" ); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_vectorequalitytest" ) ); + + c = new MeasureTransportConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_measuretransport" )); + +// c = new SimpleObjectTypeConstructor( +// MeasureTransportType::instance(), +// I18N_NOOP( "Measure Transport" ), +// I18N_NOOP( "Transport the measure of a segment or arc over a line or circle." ), +// "measuretransport" ); +// ctors->add( c ); +// actions->add( new ConstructibleAction( c, "objects_new_measuretransport" ) ); + + // the generic intersection constructor.. + c = new GenericIntersectionConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_intersection", Qt::Key_I ) ); + + // the generic tangent constructor + c = new TangentConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_tangent", Qt::Key_T ) ); + + // the generic center of curvature constructor + c = new CocConstructor(); + ctors->add( c ); + actions->add( new ConstructibleAction( c, "objects_new_centerofcurvature" ) ); + + actions->add( new ConstructPointAction( "objects_new_normalpoint" ) ); + actions->add( new ConstructTextLabelAction( "objects_new_textlabel" ) ); + actions->add( new AddFixedPointAction( "objects_new_point_xy" ) ); + +#ifdef KIG_ENABLE_PYTHON_SCRIPTING +#include "../scripting/script-common.h" + actions->add( new NewScriptAction( + I18N_NOOP( "Python Script" ), + I18N_NOOP( "Construct a new Python script." ), + "objects_new_script_python", + ScriptType::Python ) ); +#endif + +#if 0 + actions->add( new TestAction( "test_stuff" ) ); +#endif + }; + + done = true; +} diff --git a/kig/misc/builtin_stuff.h b/kig/misc/builtin_stuff.h new file mode 100644 index 00000000..198886fe --- /dev/null +++ b/kig/misc/builtin_stuff.h @@ -0,0 +1,23 @@ +// Copyright (C) 2003 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. + +#ifndef KIG_MISC_BUILTIN_STUFF_H +#define KIG_MISC_BUILTIN_STUFF_H + +void setupBuiltinStuff(); + +#endif diff --git a/kig/misc/calcpaths.cc b/kig/misc/calcpaths.cc new file mode 100644 index 00000000..1532715b --- /dev/null +++ b/kig/misc/calcpaths.cc @@ -0,0 +1,303 @@ +// 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 "calcpaths.h" + +#include "../objects/object_calcer.h" +#include "../objects/object_imp.h" + +#include <algorithm> + +// mp: +// The previous algorithm by Dominique had an exponential complexity +// for some constructions (e.g. a sequence of "n" triangles each inscribed +// into the previous). +// The new version is directly taken from a book of Alan Bertossi +// "Algoritmi e strutture dati" + +// temporarily disabling the new algorithm due to the freeze: +// I previously misunderstood the semantics of this function +// and thought that the os vector had to be completed with all +// the subtree generated by it. On the contrary, the os vector +// contains *all* the objects that we want, we only have to +// reorder them. Now it *should* work, however we postpone +// activating this to a more proper moment + +// to deactivate the new algorithm change "define" into "undef" + +#define NEWCALCPATH +#ifdef NEWCALCPATH +void localdfs( ObjectCalcer* obj, + std::vector<ObjectCalcer*>& visited, + std::vector<ObjectCalcer*>& all); + +std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& os ) +{ + // "all" is the Objects var we're building, in reverse ordering + std::vector<ObjectCalcer*> visited; + std::vector<ObjectCalcer*> all; + + for ( std::vector<ObjectCalcer*>::const_iterator i = os.begin(); i != os.end(); ++i ) + { + if ( std::find( visited.begin(), visited.end(), *i ) == visited.end() ) + { + localdfs( *i, visited, all ); + } + } + + // now, we need to remove all objects that are not in os + // (forgot to do this in previous fix :-( ) + std::vector<ObjectCalcer*> ret; + for ( std::vector<ObjectCalcer*>::reverse_iterator i = all.rbegin(); i != all.rend(); ++i ) + { + // we only add objects that appear in os + if ( std::find( os.begin(), os.end(), *i ) != os.end() ) ret.push_back( *i ); + }; + return ret; +} + +void localdfs( ObjectCalcer* obj, + std::vector<ObjectCalcer*>& visited, + std::vector<ObjectCalcer*>& all) +{ + visited.push_back( obj ); + const std::vector<ObjectCalcer*> o = obj->children(); + for ( std::vector<ObjectCalcer*>::const_iterator i = o.begin(); i != o.end(); ++i ) + { + if ( std::find( visited.begin(), visited.end(), *i ) == visited.end() ) + localdfs( *i, visited, all ); + } + all.push_back( obj ); +} + +// old calcPath commented out... + +#else +// these first two functions were written before i read stuff about +// graph theory and algorithms, so i'm sure they're far from optimal. +// However, they seem to work fine, and i don't think there's a real +// need for optimisation here.. +std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& os ) +{ + // this is a little experiment of mine, i don't know if it is the + // fastest way to do it, but it seems logical to me... + + // the general idea here: + // first we build a new Objects variable. For every object in os, + // we put all of its children at the end of it, and we do the same + // for the ones we add.. + + // "all" is the Objects var we're building... + std::vector<ObjectCalcer*> all = os; + // tmp is the var containing the objects we're iterating over. The + // first time around this is the os variable, the next time, this + // contains the variables we added in the first round... + std::vector<ObjectCalcer*> tmp = os; + // tmp2 is a temporary var. During a round, it receives all the + // variables we add ( to "all" ) in that round, and at the end of + // the round, it is assigned to tmp. + std::vector<ObjectCalcer*> tmp2; + while ( ! tmp.empty() ) + { + for ( std::vector<ObjectCalcer*>::const_iterator i = tmp.begin(); i != tmp.end(); ++i ) + { + const std::vector<ObjectCalcer*> o = (*i)->children(); + std::copy( o.begin(), o.end(), std::back_inserter( all ) ); + std::copy( o.begin(), o.end(), std::back_inserter( tmp2 ) ); + }; + tmp = tmp2; + tmp2.clear(); + }; + + // now we know that if all objects appear at least once after all of + // their parents. So, we take all, and of every object, we remove + // every reference except the last one... + std::vector<ObjectCalcer*> ret; + ret.reserve( os.size() ); + for ( std::vector<ObjectCalcer*>::reverse_iterator i = all.rbegin(); i != all.rend(); ++i ) + { + // we only add objects that appear in os and only if they are not + // already in ret.. + if ( std::find( ret.begin(), ret.end(), *i ) == ret.end() && + std::find( os.begin(), os.end(), *i ) != os.end() ) ret.push_back( *i ); + }; + std::reverse( ret.begin(), ret.end() ); + return ret; +} +#endif + +bool addBranch( const std::vector<ObjectCalcer*>& o, const ObjectCalcer* to, std::vector<ObjectCalcer*>& ret ) +{ + bool rb = false; + for ( std::vector<ObjectCalcer*>::const_iterator i = o.begin(); i != o.end(); ++i ) + { + if ( *i == to ) + rb = true; + else + if ( addBranch( (*i)->children(), to, ret ) ) + { + rb = true; + ret.push_back( *i ); + }; + }; + return rb; +} + +std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ) +{ + std::vector<ObjectCalcer*> all; + + for ( std::vector<ObjectCalcer*>::const_iterator i = from.begin(); i != from.end(); ++i ) + { + (void) addBranch( (*i)->children(), to, all ); + }; + + std::vector<ObjectCalcer*> ret; + for ( std::vector<ObjectCalcer*>::iterator i = all.begin(); i != all.end(); ++i ) + { + if ( std::find( ret.begin(), ret.end(), *i ) == ret.end() ) + ret.push_back( *i ); + }; + return std::vector<ObjectCalcer*>( ret.rbegin(), ret.rend() ); +} + +static void addNonCache( ObjectCalcer* o, std::vector<ObjectCalcer*>& ret ) +{ + if ( ! o->imp()->isCache() ) + if ( std::find( ret.begin(), ret.end(), o ) == ret.end() ) + ret.push_back( o ); + else + { + std::vector<ObjectCalcer*> parents = o->parents(); + for ( uint i = 0; i < parents.size(); ++i ) + addNonCache( parents[i], ret ); + }; +} + +static bool visit( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& from, std::vector<ObjectCalcer*>& ret ) +{ + // this function returns true if the visited object depends on one + // of the objects in from. If we encounter objects that are on the + // side of the tree path ( they do not depend on from themselves, + // but their direct children do ), then we add them to ret. + if ( std::find( from.begin(), from.end(), o ) != from.end() ) return true; + + std::vector<bool> deps( o->parents().size(), false ); + bool somedepend = false; + bool alldepend = true; + std::vector<ObjectCalcer*> parents = o->parents(); + for ( uint i = 0; i < parents.size(); ++i ) + { + bool v = visit( parents[i], from, ret ); + somedepend |= v; + alldepend &= v; + deps[i] = v; + }; + if ( somedepend && ! alldepend ) + { + for ( uint i = 0; i < deps.size(); ++i ) + if ( ! deps[i] ) + addNonCache( parents[i], ret ); + }; + + return somedepend; +} + +std::vector<ObjectCalcer*> sideOfTreePath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ) +{ + std::vector<ObjectCalcer*> ret; + visit( to, from, ret ); + return ret; +} + +std::vector<ObjectCalcer*> getAllParents( const std::vector<ObjectCalcer*>& objs ) +{ + using namespace std; + std::set<ObjectCalcer*> ret( objs.begin(),objs.end() ); + std::set<ObjectCalcer*> cur = ret; + while ( ! cur.empty() ) + { + std::set<ObjectCalcer*> next; + for ( std::set<ObjectCalcer*>::const_iterator i = cur.begin(); i != cur.end(); ++i ) + { + std::vector<ObjectCalcer*> parents = (*i)->parents(); + next.insert( parents.begin(), parents.end() ); + }; + + ret.insert( next.begin(), next.end() ); + cur = next; + }; + return std::vector<ObjectCalcer*>( ret.begin(), ret.end() ); +} + +std::vector<ObjectCalcer*> getAllParents( ObjectCalcer* obj ) +{ + std::vector<ObjectCalcer*> objs; + objs.push_back( obj ); + return getAllParents( objs ); +} + +bool isChild( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& os ) +{ + std::vector<ObjectCalcer*> parents = o->parents(); + std::set<ObjectCalcer*> cur( parents.begin(), parents.end() ); + while ( ! cur.empty() ) + { + std::set<ObjectCalcer*> next; + for ( std::set<ObjectCalcer*>::const_iterator i = cur.begin(); i != cur.end(); ++i ) + { + if ( std::find( os.begin(), os.end(), *i ) != os.end() ) return true; + std::vector<ObjectCalcer*> parents = (*i)->parents(); + next.insert( parents.begin(), parents.end() ); + }; + cur = next; + }; + return false; +} + +std::set<ObjectCalcer*> getAllChildren( ObjectCalcer* obj ) +{ + std::vector<ObjectCalcer*> objs; + objs.push_back( obj ); + return getAllChildren( objs ); +} + +std::set<ObjectCalcer*> getAllChildren( const std::vector<ObjectCalcer*> objs ) +{ + std::set<ObjectCalcer*> ret; + // objects to iterate over... + std::set<ObjectCalcer*> cur( objs.begin(), objs.end() ); + while( !cur.empty() ) + { + // contains the objects to iterate over the next time around... + std::set<ObjectCalcer*> next; + for( std::set<ObjectCalcer*>::iterator i = cur.begin(); + i != cur.end(); ++i ) + { + ret.insert( *i ); + std::vector<ObjectCalcer*> children = (*i)->children(); + next.insert( children.begin(), children.end() ); + }; + cur = next; + }; + return ret; +} + +bool isPointOnCurve( const ObjectCalcer* point, const ObjectCalcer* curve ) +{ + return point->isDefinedOnOrThrough( curve ) || curve->isDefinedOnOrThrough( point ); +} diff --git a/kig/misc/calcpaths.h b/kig/misc/calcpaths.h new file mode 100644 index 00000000..620558a3 --- /dev/null +++ b/kig/misc/calcpaths.h @@ -0,0 +1,88 @@ +// 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. + +#ifndef KIG_MISC_CALCPATHS_H +#define KIG_MISC_CALCPATHS_H + +#include "../objects/common.h" + +/** + * This function sorts \p os such that they're in the right order for + * calc()-ing. This means that child objects must appear after their + * parents ( for you graph people, this is just a topological sort.. ) + */ +std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& os ); + +/** + * This is a different function for more or less the same purpose. It + * takes a few Objects, which are considered to have been calced + * already. Then, it puts the necessary part of their children in the + * right order, so that calc()-ing correctly updates all of their data + * ( they're calc'ed in the right order, i mean... ). The objects in + * from are normally not included in the output, unless they appear + * somewhere in the middle of the calc-path towards to... + */ +std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ); + +/** + * This function returns all objects on the side of the path through + * the dependency tree from from down to to. This means that we look + * for any objects that don't depend on any of the objects in from + * themselves, but of which one of the direct children does. We need + * this function for Locus stuff... + */ +std::vector<ObjectCalcer*> sideOfTreePath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ); + +/** + * This function returns all objects above the \p given in the + * dependency graph. The \p given objects are also included + * themselves.. + */ +std::vector<ObjectCalcer*> getAllParents( const std::vector<ObjectCalcer*>& objs ); +/** + * \overload + */ +std::vector<ObjectCalcer*> getAllParents( ObjectCalcer* obj ); + +/** + * This function returns all objects below the objects in \p objs in the + * dependency graphy. The objects in \p objs are also included + * themselves.. + */ +std::set<ObjectCalcer*> getAllChildren( const std::vector<ObjectCalcer*> objs ); + +/** + * \overload + */ +std::set<ObjectCalcer*> getAllChildren( ObjectCalcer* obj ); + +/** + * Returns true if \p o is a descendant of any of the objects in \p os.. + */ +bool isChild( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& os ); + +/** + * Return true if the given \p point is ( by construction ) on the given + * \p curve. This means that it is either a constrained point on the + * curve, or the curve is constructed through the point, or the point + * is an intersection point of the curve with another curve. + * Note that it is assumed that the given point is in fact a point and the + * given curve is in fact a curve. + */ +bool isPointOnCurve( const ObjectCalcer* point, const ObjectCalcer* curve ); + +#endif diff --git a/kig/misc/common.cpp b/kig/misc/common.cpp new file mode 100644 index 00000000..fccd384f --- /dev/null +++ b/kig/misc/common.cpp @@ -0,0 +1,520 @@ +/** + 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 "common.h" + +#include "../kig/kig_view.h" +#include "../objects/object_imp.h" + +#include <cmath> + +#include <kdebug.h> +#include <knumvalidator.h> +#include <klocale.h> +#if KDE_IS_VERSION( 3, 1, 90 ) +#include <kinputdialog.h> +#else +#include <klineeditdlg.h> +#endif + +Coordinate calcPointOnPerpend( const LineData& l, const Coordinate& t ) +{ + return calcPointOnPerpend( l.b - l.a, t ); +} + +Coordinate calcPointOnPerpend( const Coordinate& dir, const Coordinate& t ) +{ + return t + ( dir ).orthogonal(); +} + +Coordinate calcPointOnParallel( const LineData& l, const Coordinate& t ) +{ + return calcPointOnParallel( l.b - l.a, t ); +} + +Coordinate calcPointOnParallel( const Coordinate& dir, const Coordinate& t ) +{ + return t + dir*5; +} + +Coordinate calcIntersectionPoint( const LineData& l1, const LineData& l2 ) +{ + const Coordinate& pa = l1.a; + const Coordinate& pb = l1.b; + const Coordinate& pc = l2.a; + const Coordinate& pd = l2.b; + + double + xab = pb.x - pa.x, + xdc = pd.x - pc.x, + xac = pc.x - pa.x, + yab = pb.y - pa.y, + ydc = pd.y - pc.y, + yac = pc.y - pa.y; + + double det = xab*ydc - xdc*yab; + double detn = xac*ydc - xdc*yac; + + // test for parallelism + if ( fabs (det) < 1e-6 ) return Coordinate::invalidCoord(); + double t = detn/det; + + return pa + t*(pb - pa); +} + +void calcBorderPoints( Coordinate& p1, Coordinate& p2, const Rect& r ) +{ + calcBorderPoints( p1.x, p1.y, p2.x, p2.y, r ); +} + +const LineData calcBorderPoints( const LineData& l, const Rect& r ) +{ + LineData ret( l ); + calcBorderPoints( ret.a.x, ret.a.y, ret.b.x, ret.b.y, r ); + return ret; +} + +void calcBorderPoints( double& xa, double& ya, double& xb, double& yb, const Rect& r ) +{ + // we calc where the line through a(xa,ya) and b(xb,yb) intersects with r: + double left = (r.left()-xa)*(yb-ya)/(xb-xa)+ya; + double right = (r.right()-xa)*(yb-ya)/(xb-xa)+ya; + double top = (r.top()-ya)*(xb-xa)/(yb-ya)+xa; + double bottom = (r.bottom()-ya)*(xb-xa)/(yb-ya)+xa; + + // now we go looking for valid points + int novp = 0; // number of valid points we have already found + + if (!(top < r.left() || top > r.right())) { + // the line intersects with the top side of the rect. + ++novp; + xa = top; ya = r.top(); + }; + if (!(left < r.bottom() || left > r.top())) { + // the line intersects with the left side of the rect. + if (novp++) { xb = r.left(); yb=left; } + else { xa = r.left(); ya=left; }; + }; + if (!(right < r.bottom() || right > r.top())) { + // the line intersects with the right side of the rect. + if (novp++) { xb = r.right(); yb=right; } + else { xa = r.right(); ya=right; }; + }; + if (!(bottom < r.left() || bottom > r.right())) { + // the line intersects with the bottom side of the rect. + ++novp; + xb = bottom; yb = r.bottom(); + }; + if (novp < 2) { + // line is completely outside of the window... + xa = ya = xb = yb = 0; + }; +} + +void calcRayBorderPoints( const Coordinate& a, Coordinate& b, const Rect& r ) +{ + calcRayBorderPoints( a.x, a.y, b.x, b.y, r ); +} + +void calcRayBorderPoints( const double xa, const double ya, double& xb, + double& yb, const Rect& r ) +{ + // we calc where the line through a(xa,ya) and b(xb,yb) intersects with r: + double left = (r.left()-xa)*(yb-ya)/(xb-xa)+ya; + double right = (r.right()-xa)*(yb-ya)/(xb-xa)+ya; + double top = (r.top()-ya)*(xb-xa)/(yb-ya)+xa; + double bottom = (r.bottom()-ya)*(xb-xa)/(yb-ya)+xa; + + // now we see which we can use... + if( + // the ray intersects with the top side of the rect.. + top >= r.left() && top <= r.right() + // and b is above a + && yb > ya ) + { + xb = top; + yb = r.top(); + return; + }; + if( + // the ray intersects with the left side of the rect... + left >= r.bottom() && left <= r.top() + // and b is on the left of a.. + && xb < xa ) + { + xb = r.left(); + yb=left; + return; + }; + if ( + // the ray intersects with the right side of the rect... + right >= r.bottom() && right <= r.top() + // and b is to the right of a.. + && xb > xa ) + { + xb = r.right(); + yb=right; + return; + }; + if( + // the ray intersects with the bottom side of the rect... + bottom >= r.left() && bottom <= r.right() + // and b is under a.. + && yb < ya ) { + xb = bottom; + yb = r.bottom(); + return; + }; + kdError() << k_funcinfo << "damn" << endl; +} + +bool isOnLine( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ) +{ + double x1 = a.x; + double y1 = a.y; + double x2 = b.x; + double y2 = b.y; + + // check your math theory ( homogeneous coördinates ) for this + double tmp = fabs( o.x * (y1-y2) + o.y*(x2-x1) + (x1*y2-y1*x2) ); + return tmp < ( fault * (b-a).length()); + // if o is on the line ( if the determinant of the matrix + // |---|---|---| + // | x | y | z | + // |---|---|---| + // | x1| y1| z1| + // |---|---|---| + // | x2| y2| z2| + // |---|---|---| + // equals 0, then p(x,y,z) is on the line containing points + // p1(x1,y1,z1) and p2 here, we're working with normal coords, no + // homogeneous ones, so all z's equal 1 +} + +bool isOnSegment( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ) +{ + return isOnLine( o, a, b, fault ) + // not too far to the right + && (o.x - kigMax(a.x,b.x) < fault ) + // not too far to the left + && ( kigMin (a.x, b.x) - o.x < fault ) + // not too high + && ( kigMin (a.y, b.y) - o.y < fault ) + // not too low + && ( o.y - kigMax (a.y, b.y) < fault ); +} + +bool isOnRay( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ) +{ + return isOnLine( o, a, b, fault ) + // not too far in front of a horizontally.. +// && ( a.x - b.x < fault ) == ( a.x - o.x < fault ) + && ( ( a.x < b.x ) ? ( a.x - o.x < fault ) : ( a.x - o.x > -fault ) ) + // not too far in front of a vertically.. +// && ( a.y - b.y < fault ) == ( a.y - o.y < fault ); + && ( ( a.y < b.y ) ? ( a.y - o.y < fault ) : ( a.y - o.y > -fault ) ); +} + +bool isOnArc( const Coordinate& o, const Coordinate& c, const double r, + const double sa, const double a, const double fault ) +{ + if ( fabs( ( c - o ).length() - r ) > fault ) + return false; + Coordinate d = o - c; + double angle = atan2( d.y, d.x ); + + if ( angle < sa ) angle += 2 * M_PI; + return angle - sa - a < 1e-4; +} + +const Coordinate calcMirrorPoint( const LineData& l, + const Coordinate& p ) +{ + Coordinate m = + calcIntersectionPoint( l, + LineData( p, + calcPointOnPerpend( l, p ) + ) + ); + return 2*m - p; +} + +const Coordinate calcCircleLineIntersect( const Coordinate& c, + const double sqr, + const LineData& l, + int side ) +{ + Coordinate proj = calcPointProjection( c, l ); + Coordinate hvec = proj - c; + Coordinate lvec = -l.dir(); + + double sqdist = hvec.squareLength(); + double sql = sqr - sqdist; + if ( sql < 0.0 ) + return Coordinate::invalidCoord(); + else + { + double l = sqrt( sql ); + lvec = lvec.normalize( l ); + lvec *= side; + + return proj + lvec; + }; +} + +const Coordinate calcArcLineIntersect( const Coordinate& c, const double sqr, + const double sa, const double angle, + const LineData& l, int side ) +{ + const Coordinate possiblepoint = calcCircleLineIntersect( c, sqr, l, side ); + if ( isOnArc( possiblepoint, c, sqrt( sqr ), sa, angle, test_threshold ) ) + return possiblepoint; + else return Coordinate::invalidCoord(); +} + +const Coordinate calcPointProjection( const Coordinate& p, + const LineData& l ) +{ + Coordinate orth = l.dir().orthogonal(); + return p + orth.normalize( calcDistancePointLine( p, l ) ); +} + +double calcDistancePointLine( const Coordinate& p, + const LineData& l ) +{ + double xa = l.a.x; + double ya = l.a.y; + double xb = l.b.x; + double yb = l.b.y; + double x = p.x; + double y = p.y; + double norm = l.dir().length(); + return ( yb * x - ya * x - xb * y + xa * y + xb * ya - yb * xa ) / norm; +} + +Coordinate calcRotatedPoint( const Coordinate& a, const Coordinate& c, const double arc ) +{ + // we take a point p on a line through c and parallel with the + // X-axis.. + Coordinate p( c.x + 5, c.y ); + // we then calc the arc that ac forms with cp... + Coordinate d = a - c; + d = d.normalize(); + double aarc = std::acos( d.x ); + if ( d.y < 0 ) aarc = 2*M_PI - aarc; + + // we now take the sum of the two arcs to find the arc between + // pc and ca + double asum = aarc + arc; + + Coordinate ret( std::cos( asum ), std::sin( asum ) ); + ret = ret.normalize( ( a -c ).length() ); + return ret + c; +} + +Coordinate calcCircleRadicalStartPoint( const Coordinate& ca, const Coordinate& cb, + double sqra, double sqrb ) +{ + Coordinate direc = cb - ca; + Coordinate m = (ca + cb)/2; + + double dsq = direc.squareLength(); + double lambda = dsq == 0.0 ? 0.0 + : (sqra - sqrb) / (2*dsq); + + direc *= lambda; + return m + direc; +} + +double getDoubleFromUser( const QString& caption, const QString& label, double value, + QWidget* parent, bool* ok, double min, double max, int decimals ) +{ +#ifdef KIG_USE_KDOUBLEVALIDATOR + KDoubleValidator vtor( min, max, decimals, 0, 0 ); +#else + KFloatValidator vtor( min, max, (QWidget*) 0, 0 ); +#endif +#if KDE_IS_VERSION( 3, 1, 90 ) + QString input = KInputDialog::getText( + caption, label, KGlobal::locale()->formatNumber( value, decimals ), + ok, parent, "getDoubleFromUserDialog", &vtor ); +#else + QString input = + KLineEditDlg::getText( caption, label, + KGlobal::locale()->formatNumber( value, decimals ), + ok, parent, &vtor ); +#endif + + bool myok = true; + double ret = KGlobal::locale()->readNumber( input, &myok ); + if ( ! myok ) + ret = input.toDouble( & myok ); + if ( ok ) *ok = myok; + return ret; +} + +const Coordinate calcCenter( + const Coordinate& a, const Coordinate& b, const Coordinate& c ) +{ + // this algorithm is written by my brother, Christophe Devriese + // <oelewapperke@ulyssis.org> ... + // I don't get it myself :) + + double xdo = b.x-a.x; + double ydo = b.y-a.y; + + double xao = c.x-a.x; + double yao = c.y-a.y; + + double a2 = xdo*xdo + ydo*ydo; + double b2 = xao*xao + yao*yao; + + double numerator = (xdo * yao - xao * ydo); + if ( numerator == 0 ) + { + // problem: xdo * yao == xao * ydo <=> xdo/ydo == xao / yao + // this means that the lines ac and ab have the same direction, + // which means they're the same line.. + // FIXME: i would normally throw an error here, but KDE doesn't + // use exceptions, so i'm returning a bogus point :( + return a.invalidCoord(); + /* return (a+c)/2; */ + }; + double denominator = 0.5 / numerator; + + double centerx = a.x - (ydo * b2 - yao * a2) * denominator; + double centery = a.y + (xdo * b2 - xao * a2) * denominator; + + return Coordinate(centerx, centery); +} + +bool lineInRect( const Rect& r, const Coordinate& a, const Coordinate& b, + const int width, const ObjectImp* imp, const KigWidget& w ) +{ + double miss = w.screenInfo().normalMiss( width ); + +//mp: the following test didn't work for vertical segments; +// fortunately the ieee floating point standard allows us to avoid +// the test altogether, since it would produce an infinity value that +// makes the final r.contains to fail +// in any case the corresponding test for a.y - b.y was missing. + +// if ( fabs( a.x - b.x ) <= 1e-7 ) +// { +// // too small to be useful.. +// return r.contains( Coordinate( a.x, r.center().y ), miss ); +// } + +// in case we have a segment we need also to check for the case when +// the segment is entirely contained in the rect, in which case the +// final tests all fail. +// it is ok to just check for the midpoint in the rect since: +// - if we have a segment completely contained in the rect this is true +// - if the midpoint is in the rect than returning true is safe (also +// in the cases where we have a ray or a line) + + if ( r.contains( 0.5*( a + b ), miss ) ) return true; + + Coordinate dir = b - a; + double m = dir.y / dir.x; + double lefty = a.y + m * ( r.left() - a.x ); + double righty = a.y + m * ( r.right() - a.x ); + double minv = dir.x / dir.y; + double bottomx = a.x + minv * ( r.bottom() - a.y ); + double topx = a.x + minv * ( r.top() - a.y ); + + // these are the intersections between the line, and the lines + // defined by the sides of the rectangle. + Coordinate leftint( r.left(), lefty ); + Coordinate rightint( r.right(), righty ); + Coordinate bottomint( bottomx, r.bottom() ); + Coordinate topint( topx, r.top() ); + + // For each intersection, we now check if we contain the + // intersection ( this might not be the case for a segment, when the + // intersection is not between the begin and end point.. ) and if + // the rect contains the intersection.. If it does, we have a winner.. + return + ( imp->contains( leftint, width, w ) && r.contains( leftint, miss ) ) || + ( imp->contains( rightint, width, w ) && r.contains( rightint, miss ) ) || + ( imp->contains( bottomint, width, w ) && r.contains( bottomint, miss ) ) || + ( imp->contains( topint, width, w ) && r.contains( topint, miss ) ); +} + +bool operator==( const LineData& l, const LineData& r ) +{ + return l.a == r.a && l.b == r.b; +} + +bool LineData::isParallelTo( const LineData& l ) const +{ + const Coordinate& p1 = a; + const Coordinate& p2 = b; + const Coordinate& p3 = l.a; + const Coordinate& p4 = l.b; + + double dx1 = p2.x - p1.x; + double dy1 = p2.y - p1.y; + double dx2 = p4.x - p3.x; + double dy2 = p4.y - p3.y; + + return isSingular( dx1, dy1, dx2, dy2 ); +} + +bool LineData::isOrthogonalTo( const LineData& l ) const +{ + const Coordinate& p1 = a; + const Coordinate& p2 = b; + const Coordinate& p3 = l.a; + const Coordinate& p4 = l.b; + + double dx1 = p2.x - p1.x; + double dy1 = p2.y - p1.y; + double dx2 = p4.x - p3.x; + double dy2 = p4.y - p3.y; + + return isSingular( dx1, dy1, -dy2, dx2 ); +} + +bool areCollinear( const Coordinate& p1, + const Coordinate& p2, const Coordinate& p3 ) +{ + return isSingular( p2.x - p1.x, p2.y - p1.y, p3.x - p1.x, p3.y - p1.y ); +} + +bool isSingular( const double& a, const double& b, + const double& c, const double& d ) +{ + double det = a*d - b*c; + double norm1 = std::fabs(a) + std::fabs(b); + double norm2 = std::fabs(c) + std::fabs(d); + +/* + * test must be done relative to the magnitude of the two + * row (or column) vectors! + */ + return ( std::fabs(det) < test_threshold*norm1*norm2 ); +} + +const double double_inf = HUGE_VAL; +const double test_threshold = 1e-6; diff --git a/kig/misc/common.h b/kig/misc/common.h new file mode 100644 index 00000000..77a1faa2 --- /dev/null +++ b/kig/misc/common.h @@ -0,0 +1,291 @@ +/** + 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 +**/ + + +#ifndef KIG_MISC_COMMON_H +#define KIG_MISC_COMMON_H + +#include "coordinate.h" +#include "rect.h" + +#include <qrect.h> +#include <kdeversion.h> + +#include <vector> +#include <assert.h> + +#ifdef KDE_IS_VERSION +#if KDE_IS_VERSION( 3, 1, 0 ) +#define KIG_USE_KDOUBLEVALIDATOR +#else +#undef KIG_USE_KDOUBLEVALIDATOR +#endif +#else +#undef KIG_USE_KDOUBLEVALIDATOR +#endif + +class ObjectImp; +class KigWidget; + +extern const double double_inf; + +/** + * Here, we define some algorithms which we need in + * various places... + */ + +double getDoubleFromUser( const QString& caption, const QString& label, double value, + QWidget* parent, bool* ok, double min, double max, int decimals ); + +/** + * Simple class representing a line. Used by various functions in Kig. + */ +class LineData { +public: + /** + * \ifnot creating-python-scripting-doc + * Default constructor. Sets a and b to the origin. + * \endif + */ + LineData() : a(), b() {} + /** + * Constructor. Sets a and b to the given Coordinates. + */ + LineData( const Coordinate& na, const Coordinate& nb ) : a( na ), b( nb ) {} + /** + * One point on the line. + */ + Coordinate a; + /** + * Another point on the line. + */ + Coordinate b; + /** + * The direction of the line. Equivalent to b - a. + */ + const Coordinate dir() const { return b - a; } + /** + * The length from a to b. + */ + double length() const { return ( b - a ).length(); } + + /** + * Return true if this line is parallel to l. + */ + bool isParallelTo( const LineData& l ) const; + + /** + * Return true if this line is orthogonal to l. + */ + bool isOrthogonalTo( const LineData& l ) const; +}; + +/** + * Equality. Tests two LineData's for equality. + */ +bool operator==( const LineData& l, const LineData& r ); + +/** + * This calcs the rotation of point a around point c by arc arc. Arc + * is in radians, in the range 0 < arc < 2*pi ... + */ +Coordinate calcRotatedPoint( const Coordinate& a, const Coordinate& c, const double arc ); + +/** + * this returns a point, so that the line through point t + * and the point returned is perpendicular to the line l. + */ +Coordinate calcPointOnPerpend( const LineData& l, const Coordinate& t ); + +/** + * this returns a point, so that the line through point t and the + * point returned is perpendicular to the direction given in dir... + */ +Coordinate calcPointOnPerpend( const Coordinate& dir, const Coordinate& t ); + +/** + * this returns a point, so that the line through point t + * and the point returned is parallel with the line l + */ +Coordinate calcPointOnParallel( const LineData& l, const Coordinate& t ); + +/** + * this returns a point, so that the line through point t + * and the point returned is parallel with the direction given in dir... + */ +Coordinate calcPointOnParallel( const Coordinate& dir, const Coordinate& t ); + + +/** + * this calcs the point where the lines l and m intersect... + */ +Coordinate calcIntersectionPoint( const LineData& l, const LineData& m ); + +/** + * this calcs the intersection points of the circle with center c and + * radius sqrt( r ), and the line l. As a circle and a + * line have two intersection points, side tells us which one we + * need... It should be 1 or -1. If the line and the circle have no + * intersection, valid is set to false, otherwise to true... + * Note that sqr is the _square_ of the radius. We do this to avoid + * rounding errors... + */ +const Coordinate calcCircleLineIntersect( const Coordinate& c, + const double sqr, + const LineData& l, + int side ); + +/** + * this calcs the intersection points of the arc with center c, + * radius sqrt( r ), start angle sa and angle angle, and the line l. + * As a arc and a line can have max two intersection points, side + * tells us which one we need... It should be 1 or -1. If the line + * and the arc have no intersection, valid is set to false, otherwise + * to true... Note that sqr is the _square_ of the radius. We do + * this to avoid rounding errors... + */ +const Coordinate calcArcLineIntersect( const Coordinate& c, const double sqr, + const double sa, const double angle, + const LineData& l, int side ); + +/** + * this calculates the perpendicular projection of point p on line + * ab... + */ +const Coordinate calcPointProjection( const Coordinate& p, + const LineData& l ); + +/** + * calc the distance of point p to the line through a and b... + */ +double calcDistancePointLine( const Coordinate& p, + const LineData& l ); + +/** + * this sets p1 and p2 to p1' and p2' so that p1'p2' is the same line + * as p1p2, and so that p1' and p2' are on the border of the Rect... + */ +void calcBorderPoints( Coordinate& p1, Coordinate& p2, const Rect& r ); +/** + * overload... + */ +void calcBorderPoints( double& xa, double& xb, double& ya, double& yb, const Rect& r); +/** + * cleaner overload, intended to replace the above two... + */ +const LineData calcBorderPoints( const LineData& l, const Rect& r ); + +/** + * this does the same as the above function, but only for b.. + */ +void calcRayBorderPoints( const Coordinate& a, Coordinate& b, const Rect& r ); + +/** + * This function calculates the center of the circle going through the + * three given points.. + */ +const Coordinate calcCenter( + const Coordinate& a, const Coordinate& b, const Coordinate& c ); + +/** + * overload... + */ +void calcRayBorderPoints( const double xa, const double xb, double& ya, + double& yb, const Rect& r ); + +/** + * calc the mirror point of p over the line l + */ +const Coordinate calcMirrorPoint( const LineData& l, + const Coordinate& p ); + +/** + * test collinearity of three points + */ +bool areCollinear( const Coordinate& p1, const Coordinate& p2, + const Coordinate& p3 ); + +/** + * test if a 2x2 matrix is singular (relatively to the + * norm of the two row vectors) + */ +bool isSingular( const double& a, const double& b, + const double& c, const double& d ); + +/** + * is o on the line defined by point a and point b ? + * fault is the allowed difference... + */ +bool isOnLine( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ); + +/** + * is o on the segment defined by point a and point b ? + * this calls isOnLine(), but also checks if o is "between" a and b... + * fault is the allowed difference... + */ +bool isOnSegment( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ); + +bool isOnRay( const Coordinate& o, const Coordinate& a, + const Coordinate& b, const double fault ); + +bool isOnArc( const Coordinate& o, const Coordinate& c, const double r, + const double sa, const double a, const double fault ); + +Coordinate calcCircleRadicalStartPoint( const Coordinate& ca, + const Coordinate& cb, + double sqra, double sqrb ); + +/** + * Is the line, segment, ray or vector inside r ? We need the imp to + * distinguish between rays, lines, segments or whatever.. ( we use + * their contains functions actually.. ) + */ +bool lineInRect( const Rect& r, const Coordinate& a, const Coordinate& b, + const int width, const ObjectImp* imp, const KigWidget& w ); + +template <typename T> +T kigMin( const T& a, const T& b ) +{ + return a < b ? a : b; +} + +template <typename T> +T kigMax( const T& a, const T& b ) +{ + return a > b ? a : b; +} + +template <typename T> +T kigAbs( const T& a ) +{ + return a >= 0 ? a : -a; +} + +template <typename T> +int kigSgn( const T& a ) +{ + return a == 0 ? 0 : a > 0 ? +1 : -1; +} + +extern const double test_threshold; + +#endif diff --git a/kig/misc/conic-common.cpp b/kig/misc/conic-common.cpp new file mode 100644 index 00000000..3dde7449 --- /dev/null +++ b/kig/misc/conic-common.cpp @@ -0,0 +1,888 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + + 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 <config.h> + +#include "conic-common.h" + +#include "common.h" +#include "kigtransform.h" + +#include <cmath> +#include <algorithm> + +#ifdef HAVE_IEEEFP_H +#include <ieeefp.h> +#endif + +ConicCartesianData::ConicCartesianData( + const ConicPolarData& polardata + ) +{ + double ec = polardata.ecostheta0; + double es = polardata.esintheta0; + double p = polardata.pdimen; + double fx = polardata.focus1.x; + double fy = polardata.focus1.y; + + double a = 1 - ec*ec; + double b = 1 - es*es; + double c = - 2*ec*es; + double d = - 2*p*ec; + double e = - 2*p*es; + double f = - p*p; + + f += a*fx*fx + b*fy*fy + c*fx*fy - d*fx - e*fy; + d -= 2*a*fx + c*fy; + e -= 2*b*fy + c*fx; + + coeffs[0] = a; + coeffs[1] = b; + coeffs[2] = c; + coeffs[3] = d; + coeffs[4] = e; + coeffs[5] = f; +} + +ConicPolarData::ConicPolarData( const ConicCartesianData& cartdata ) +{ + double a = cartdata.coeffs[0]; + double b = cartdata.coeffs[1]; + double c = cartdata.coeffs[2]; + double d = cartdata.coeffs[3]; + double e = cartdata.coeffs[4]; + double f = cartdata.coeffs[5]; + + // 1. Compute theta (tilt of conic) + double theta = std::atan2(c, b - a)/2; + + // now I should possibly add pi/2... + double costheta = std::cos(theta); + double sintheta = std::sin(theta); + // compute new coefficients (c should now be zero) + double aa = a*costheta*costheta + b*sintheta*sintheta - c*sintheta*costheta; + double bb = a*sintheta*sintheta + b*costheta*costheta + c*sintheta*costheta; + if (aa*bb < 0) + { // we have a hyperbola we need to check the correct orientation + double dd = d*costheta - e*sintheta; + double ee = d*sintheta + e*costheta; + double xc = - dd / ( 2*aa ); + double yc = - ee / ( 2*bb ); + double ff = f + aa*xc*xc + bb*yc*yc + dd*xc + ee*yc; + if (ff*aa > 0) // wrong orientation + { + if (theta > 0) theta -= M_PI/2; + else theta += M_PI/2; + costheta = cos(theta); + sintheta = sin(theta); + aa = a*costheta*costheta + b*sintheta*sintheta - c*sintheta*costheta; + bb = a*sintheta*sintheta + b*costheta*costheta + c*sintheta*costheta; + } + } + else + { + if ( std::fabs (bb) < std::fabs (aa) ) + { + if (theta > 0) theta -= M_PI/2; + else theta += M_PI/2; + costheta = cos(theta); + sintheta = sin(theta); + aa = a*costheta*costheta + b*sintheta*sintheta - c*sintheta*costheta; + bb = a*sintheta*sintheta + b*costheta*costheta + c*sintheta*costheta; + } + } + + double cc = 2*(a - b)*sintheta*costheta + + c*(costheta*costheta - sintheta*sintheta); + // cc should be zero!!! cout << "cc = " << cc << "\n"; + double dd = d*costheta - e*sintheta; + double ee = d*sintheta + e*costheta; + + a = aa; + b = bb; + c = cc; + d = dd; + e = ee; + + // now b cannot be zero (otherwise conic is degenerate) + a /= b; + c /= b; + d /= b; + e /= b; + f /= b; + b = 1.0; + + // 2. compute y coordinate of focuses + + double yf = - e/2; + + // new values: + f += yf*yf + e*yf; + e += 2*yf; // this should be zero! + + // now: a > 0 -> ellipse + // a = 0 -> parabula + // a < 0 -> hyperbola + + double eccentricity = sqrt(1.0 - a); + + double sqrtdiscrim = sqrt(d*d - 4*a*f); + if (d < 0.0) sqrtdiscrim = -sqrtdiscrim; + double xf = (4*a*f - 4*f - d*d)/(d + eccentricity*sqrtdiscrim) / 2; + + // 3. the focus needs to be rotated back into position + focus1.x = xf*costheta + yf*sintheta; + focus1.y = -xf*sintheta + yf*costheta; + + // 4. final touch: the pdimen + pdimen = -sqrtdiscrim/2; + + ecostheta0 = eccentricity*costheta; + esintheta0 = -eccentricity*sintheta; + if ( pdimen < 0) + { + pdimen = -pdimen; + ecostheta0 = -ecostheta0; + esintheta0 = -esintheta0; + } +} + +const ConicCartesianData calcConicThroughPoints ( + const std::vector<Coordinate>& points, + const LinearConstraints c1, + const LinearConstraints c2, + const LinearConstraints c3, + const LinearConstraints c4, + const LinearConstraints c5 ) +{ + assert( 0 < points.size() && points.size() <= 5 ); + // points is a vector of up to 5 points through which the conic is + // constrained. + // this routine should compute the coefficients in the cartesian equation + // a x^2 + b y^2 + c xy + d x + e y + f = 0 + // they are defined up to a multiplicative factor. + // since we don't know (in advance) which one of them is nonzero, we + // simply keep all 6 parameters, obtaining a 5x6 linear system which + // we solve using gaussian elimination with complete pivoting + // If there are too few, then we choose some cool way to fill in the + // empty parts in the matrix according to the LinearConstraints + // given.. + + // 5 rows, 6 columns.. + double row0[6]; + double row1[6]; + double row2[6]; + double row3[6]; + double row4[6]; + double *matrix[5] = {row0, row1, row2, row3, row4}; + double solution[6]; + int scambio[6]; + LinearConstraints constraints[] = {c1, c2, c3, c4, c5}; + + int numpoints = points.size(); + int numconstraints = 5; + + // fill in the matrix elements + for ( int i = 0; i < numpoints; ++i ) + { + double xi = points[i].x; + double yi = points[i].y; + matrix[i][0] = xi*xi; + matrix[i][1] = yi*yi; + matrix[i][2] = xi*yi; + matrix[i][3] = xi; + matrix[i][4] = yi; + matrix[i][5] = 1.0; + } + + for ( int i = 0; i < numconstraints; i++ ) + { + if (numpoints >= 5) break; // don't add constraints if we have enough + for (int j = 0; j < 6; ++j) matrix[numpoints][j] = 0.0; + // force the symmetry axes to be + // parallel to the coordinate system (zero tilt): c = 0 + if (constraints[i] == zerotilt) matrix[numpoints][2] = 1.0; + // force a parabula (if zerotilt): b = 0 + if (constraints[i] == parabolaifzt) matrix[numpoints][1] = 1.0; + // force a circle (if zerotilt): a = b + if (constraints[i] == circleifzt) { + matrix[numpoints][0] = 1.0; + matrix[numpoints][1] = -1.0; } + // force an equilateral hyperbola: a + b = 0 + if (constraints[i] == equilateral) { + matrix[numpoints][0] = 1.0; + matrix[numpoints][1] = 1.0; } + // force symmetry about y-axis: d = 0 + if (constraints[i] == ysymmetry) matrix[numpoints][3] = 1.0; + // force symmetry about x-axis: e = 0 + if (constraints[i] == xsymmetry) matrix[numpoints][4] = 1.0; + + if (constraints[i] != noconstraint) ++numpoints; + } + + if ( ! GaussianElimination( matrix, numpoints, 6, scambio ) ) + return ConicCartesianData::invalidData(); + // fine della fase di eliminazione + BackwardSubstitution( matrix, numpoints, 6, scambio, solution ); + + // now solution should contain the correct coefficients.. + return ConicCartesianData( solution ); +} + +const ConicPolarData calcConicBFFP( + const std::vector<Coordinate>& args, + int type ) +{ + assert( args.size() >= 2 && args.size() <= 3 ); + assert( type == 1 || type == -1 ); + + ConicPolarData ret; + + Coordinate f1 = args[0]; + Coordinate f2 = args[1]; + Coordinate d; + double eccentricity, d1, d2, dl; + + Coordinate f2f1 = f2 - f1; + double f2f1l = f2f1.length(); + ret.ecostheta0 = f2f1.x / f2f1l; + ret.esintheta0 = f2f1.y / f2f1l; + + if ( args.size() == 3 ) + { + d = args[2]; + d1 = ( d - f1 ).length(); + d2 = ( d - f2 ).length(); + dl = fabs( d1 + type * d2 ); + eccentricity = f2f1l/dl; + } + else + { + if ( type > 0 ) eccentricity = 0.7; else eccentricity = 2.0; + dl = f2f1l/eccentricity; + } + + double rhomax = (dl + f2f1l) /2.0; + + ret.ecostheta0 *= eccentricity; + ret.esintheta0 *= eccentricity; + ret.pdimen = type*(1 - eccentricity)*rhomax; + ret.focus1 = type == 1 ? f1 : f2; + return ret; +} + +const LineData calcConicPolarLine ( + const ConicCartesianData& data, + const Coordinate& cpole, + bool& valid ) +{ + double x = cpole.x; + double y = cpole.y; + double a = data.coeffs[0]; + double b = data.coeffs[1]; + double c = data.coeffs[2]; + double d = data.coeffs[3]; + double e = data.coeffs[4]; + double f = data.coeffs[5]; + + double alpha = 2*a*x + c*y + d; + double beta = c*x + 2*b*y + e; + double gamma = d*x + e*y + 2*f; + + double normsq = alpha*alpha + beta*beta; + + if (normsq < 1e-10) // line at infinity + { + valid = false; + return LineData(); + } + valid = true; + + Coordinate reta = -gamma/normsq * Coordinate (alpha, beta); + Coordinate retb = reta + Coordinate (-beta, alpha); + return LineData( reta, retb ); +} + +const Coordinate calcConicPolarPoint ( + const ConicCartesianData& data, + const LineData& polar ) +{ + Coordinate p1 = polar.a; + Coordinate p2 = polar.b; + + double alpha = p2.y - p1.y; + double beta = p1.x - p2.x; + double gamma = p1.y*p2.x - p1.x*p2.y; + + double a11 = data.coeffs[0]; + double a22 = data.coeffs[1]; + double a12 = data.coeffs[2]/2.0; + double a13 = data.coeffs[3]/2.0; + double a23 = data.coeffs[4]/2.0; + double a33 = data.coeffs[5]; + +// double detA = a11*a22*a33 - a11*a23*a23 - a22*a13*a13 - a33*a12*a12 + +// 2*a12*a23*a13; + + double a11inv = a22*a33 - a23*a23; + double a22inv = a11*a33 - a13*a13; + double a33inv = a11*a22 - a12*a12; + double a12inv = a23*a13 - a12*a33; + double a23inv = a12*a13 - a23*a11; + double a13inv = a12*a23 - a13*a22; + + double x = a11inv*alpha + a12inv*beta + a13inv*gamma; + double y = a12inv*alpha + a22inv*beta + a23inv*gamma; + double z = a13inv*alpha + a23inv*beta + a33inv*gamma; + + if (fabs(z) < 1e-10) // point at infinity + { + return Coordinate::invalidCoord(); + } + + x /= z; + y /= z; + return Coordinate (x, y); +} + +const Coordinate calcConicLineIntersect( const ConicCartesianData& c, + const LineData& l, + double knownparam, + int which ) +{ + assert( which == 1 || which == -1 || which == 0 ); + + double aa = c.coeffs[0]; + double bb = c.coeffs[1]; + double cc = c.coeffs[2]; + double dd = c.coeffs[3]; + double ee = c.coeffs[4]; + double ff = c.coeffs[5]; + + double x = l.a.x; + double y = l.a.y; + double dx = l.b.x - l.a.x; + double dy = l.b.y - l.a.y; + + double aaa = aa*dx*dx + bb*dy*dy + cc*dx*dy; + double bbb = 2*aa*x*dx + 2*bb*y*dy + cc*x*dy + cc*y*dx + dd*dx + ee*dy; + double ccc = aa*x*x + bb*y*y + cc*x*y + dd*x + ee*y + ff; + + double t; + if ( which == 0 ) /* i.e. we have a known intersection */ + { + t = - bbb/aaa - knownparam; + return l.a + t*(l.b - l.a); + } + + double discrim = bbb*bbb - 4*aaa*ccc; + if (discrim < 0.0) + { + return Coordinate::invalidCoord(); + } + else + { + if ( which*bbb > 0 ) + { + t = bbb + which*sqrt(discrim); + t = - 2*ccc/t; + } else { + t = -bbb + which*sqrt(discrim); + t /= 2*aaa; + } + + return l.a + t*(l.b - l.a); + } +} + +ConicPolarData::ConicPolarData( + const Coordinate& f, double d, + double ec, double es ) + : focus1( f ), pdimen( d ), ecostheta0( ec ), esintheta0( es ) +{ +} + +ConicPolarData::ConicPolarData() + : focus1(), pdimen( 0 ), ecostheta0( 0 ), esintheta0( 0 ) +{ +} + +const ConicPolarData calcConicBDFP( + const LineData& directrix, + const Coordinate& cfocus, + const Coordinate& cpoint ) +{ + ConicPolarData ret; + + Coordinate ba = directrix.dir(); + double bal = ba.length(); + ret.ecostheta0 = -ba.y / bal; + ret.esintheta0 = ba.x / bal; + + Coordinate pa = cpoint - directrix.a; + + double distpf = (cpoint - cfocus).length(); + double distpd = ( pa.y*ba.x - pa.x*ba.y)/bal; + + double eccentricity = distpf/distpd; + ret.ecostheta0 *= eccentricity; + ret.esintheta0 *= eccentricity; + + Coordinate fa = cfocus - directrix.a; + ret.pdimen = ( fa.y*ba.x - fa.x*ba.y )/bal; + ret.pdimen *= eccentricity; + ret.focus1 = cfocus; + + return ret; +} + +ConicCartesianData::ConicCartesianData( const double incoeffs[6] ) +{ + std::copy( incoeffs, incoeffs + 6, coeffs ); +} + +const LineData calcConicAsymptote( + const ConicCartesianData data, + int which, bool &valid ) +{ + assert( which == -1 || which == 1 ); + + LineData ret; + double a=data.coeffs[0]; + double b=data.coeffs[1]; + double c=data.coeffs[2]; + double d=data.coeffs[3]; + double e=data.coeffs[4]; + + double normabc = a*a + b*b + c*c; + double delta = c*c - 4*a*b; + if (fabs(delta) < 1e-6*normabc) { valid = false; return ret; } + + double yc = (2*a*e - c*d)/delta; + double xc = (2*b*d - c*e)/delta; + // let c be nonnegative; we no longer need d, e, f. + if (c < 0) + { + c *= -1; + a *= -1; + b *= -1; + } + + if ( delta < 0 ) + { + valid = false; + return ret; + } + + double sqrtdelta = sqrt(delta); + Coordinate displacement; + if (which > 0) + displacement = Coordinate(-2*b, c + sqrtdelta); + else + displacement = Coordinate(c + sqrtdelta, -2*a); + ret.a = Coordinate(xc, yc); + ret.b = ret.a + displacement; + return ret; +} + +const ConicCartesianData calcConicByAsymptotes( + const LineData& line1, + const LineData& line2, + const Coordinate& p ) +{ + Coordinate p1 = line1.a; + Coordinate p2 = line1.b; + double x = p.x; + double y = p.y; + + double c1 = p1.x*p2.y - p2.x*p1.y; + double b1 = p2.x - p1.x; + double a1 = p1.y - p2.y; + + p1 = line2.a; + p2 = line2.b; + + double c2 = p1.x*p2.y - p2.x*p1.y; + double b2 = p2.x - p1.x; + double a2 = p1.y - p2.y; + + double a = a1*a2; + double b = b1*b2; + double c = a1*b2 + a2*b1; + double d = a1*c2 + a2*c1; + double e = b1*c2 + c1*b2; + + double f = a*x*x + b*y*y + c*x*y + d*x + e*y; + f = -f; + + return ConicCartesianData( a, b, c, d, e, f ); +} + +const LineData calcConicRadical( const ConicCartesianData& cequation1, + const ConicCartesianData& cequation2, + int which, int zeroindex, bool& valid ) +{ + assert( which == 1 || which == -1 ); + assert( 0 < zeroindex && zeroindex < 4 ); + LineData ret; + valid = true; + + double a = cequation1.coeffs[0]; + double b = cequation1.coeffs[1]; + double c = cequation1.coeffs[2]; + double d = cequation1.coeffs[3]; + double e = cequation1.coeffs[4]; + double f = cequation1.coeffs[5]; + + double a2 = cequation2.coeffs[0]; + double b2 = cequation2.coeffs[1]; + double c2 = cequation2.coeffs[2]; + double d2 = cequation2.coeffs[3]; + double e2 = cequation2.coeffs[4]; + double f2 = cequation2.coeffs[5]; + +// background: the family of conics c + lambda*c2 has members that +// degenerate into a union of two lines. The values of lambda giving +// such degenerate conics is the solution of a third degree equation. +// The coefficients of such equation are given by: +// (Thanks to Dominique Devriese for the suggestion of this approach) +// domi: (And thanks to Maurizio for implementing it :) + + double df = 4*a*b*f - a*e*e - b*d*d - c*c*f + c*d*e; + double cf = 4*a2*b*f + 4*a*b2*f + 4*a*b*f2 + - 2*a*e*e2 - 2*b*d*d2 - 2*f*c*c2 + - a2*e*e - b2*d*d - f2*c*c + + c2*d*e + c*d2*e + c*d*e2; + double bf = 4*a*b2*f2 + 4*a2*b*f2 + 4*a2*b2*f + - 2*a2*e2*e - 2*b2*d2*d - 2*f2*c2*c + - a*e2*e2 - b*d2*d2 - f*c2*c2 + + c*d2*e2 + c2*d*e2 + c2*d2*e; + double af = 4*a2*b2*f2 - a2*e2*e2 - b2*d2*d2 - c2*c2*f2 + c2*d2*e2; + +// assume both conics are nondegenerate, renormalize so that af = 1 + + df /= af; + cf /= af; + bf /= af; + af = 1.0; // not needed, just for consistency + +// computing the coefficients of the Sturm sequence + + double p1a = 2*bf*bf - 6*cf; + double p1b = bf*cf - 9*df; + double p0a = cf*p1a*p1a + p1b*(3*p1b - 2*bf*p1a); + double fval, fpval, lambda; + + if (p0a < 0 && p1a < 0) + { + // -+-- ---+ + valid = false; + return ret; + } + + lambda = -bf/3.0; //inflection point + double displace = 1.0; + if (p1a > 0) // with two stationary points + { + displace += sqrt(p1a); // should be enough. The important + // thing is that it is larger than the + // semidistance between the stationary points + } + // compute the value at the inflection point using Horner scheme + fval = bf + lambda; // b + x + fval = cf + lambda*fval; // c + xb + xx + fval = df + lambda*fval; // d + xc + xxb + xxx + + if (fabs(p0a) < 1e-7) + { // this is the case if we intersect two vertical parabulas! + p0a = 1e-7; // fall back to the one zero case + } + if (p0a < 0) + { + // we have three roots.. + // we select the one we want ( defined by mzeroindex.. ) + lambda += ( 2 - zeroindex )* displace; + } + else + { + // we have just one root + if( zeroindex > 1 ) // cannot find second and third root + { + valid = false; + return ret; + } + + if (fval > 0) // zero on the left + { + lambda -= displace; + } else { // zero on the right + lambda += displace; + } + + // p0a = 0 means we have a root with multiplicity two + } + +// +// find a root of af*lambda^3 + bf*lambda^2 + cf*lambda + df = 0 +// (use a Newton method starting from lambda = 0. Hope...) +// + + double delta; + + int iterations = 0; + const int maxiterations = 30; + while (iterations++ < maxiterations) // using Newton, iterations should be very few + { + // compute value of function and polinomial + fval = fpval = af; + fval = bf + lambda*fval; // b + xa + fpval = fval + lambda*fpval; // b + 2xa + fval = cf + lambda*fval; // c + xb + xxa + fpval = fval + lambda*fpval; // c + 2xb + 3xxa + fval = df + lambda*fval; // d + xc + xxb + xxxa + + delta = fval/fpval; + lambda -= delta; + if (fabs(delta) < 1e-6) break; + } + if (iterations >= maxiterations) { valid = false; return ret; } + + // now we have the degenerate conic: a, b, c, d, e, f + + a += lambda*a2; + b += lambda*b2; + c += lambda*c2; + d += lambda*d2; + e += lambda*e2; + f += lambda*f2; + + // domi: + // this is the determinant of the matrix of the new conic. It + // should be zero, for the new conic to be degenerate. + df = 4*a*b*f - a*e*e - b*d*d - c*c*f + c*d*e; + + //lets work in homogeneous coordinates... + + double dis1 = e*e - 4*b*f; + double maxval = fabs(dis1); + int maxind = 1; + double dis2 = d*d - 4*a*f; + if (fabs(dis2) > maxval) + { + maxval = fabs(dis2); + maxind = 2; + } + double dis3 = c*c - 4*a*b; + if (fabs(dis3) > maxval) + { + maxval = fabs(dis3); + maxind = 3; + } + // one of these must be nonzero (otherwise the matrix is ...) + // exchange elements so that the largest is the determinant of the + // first 2x2 minor + double temp; + switch (maxind) + { + case 1: // exchange 1 <-> 3 + temp = a; a = f; f = temp; + temp = c; c = e; e = temp; + temp = dis1; dis1 = dis3; dis3 = temp; + break; + + case 2: // exchange 2 <-> 3 + temp = b; b = f; f = temp; + temp = c; c = d; d = temp; + temp = dis2; dis2 = dis3; dis3 = temp; + break; + } + + // domi: + // this is the negative of the determinant of the top left of the + // matrix. If it is 0, then the conic is a parabola, if it is < 0, + // then the conic is an ellipse, if positive, the conic is a + // hyperbola. In this case, it should be positive, since we have a + // degenerate conic, which is a degenerate case of a hyperbola.. + // note that it is negative if there is no valid conic to be + // found ( e.g. not enough intersections.. ) + // double discrim = c*c - 4*a*b; + + if (dis3 < 0) + { + // domi: + // i would put an assertion here, but well, i guess it doesn't + // really matter, and this prevents crashes if the math i still + // recall from high school happens to be wrong :) + valid = false; + return ret; + }; + + double r[3]; // direction of the null space + r[0] = 2*b*d - c*e; + r[1] = 2*a*e - c*d; + r[2] = dis3; + + // now remember the switch: + switch (maxind) + { + case 1: // exchange 1 <-> 3 + temp = a; a = f; f = temp; + temp = c; c = e; e = temp; + temp = dis1; dis1 = dis3; dis3 = temp; + temp = r[0]; r[0] = r[2]; r[2] = temp; + break; + + case 2: // exchange 2 <-> 3 + temp = b; b = f; f = temp; + temp = c; c = d; d = temp; + temp = dis2; dis2 = dis3; dis3 = temp; + temp = r[1]; r[1] = r[2]; r[2] = temp; + break; + } + + // Computing a Householder reflection transformation that + // maps r onto [0, 0, k] + + double w[3]; + double rnormsq = r[0]*r[0] + r[1]*r[1] + r[2]*r[2]; + double k = sqrt( rnormsq ); + if ( k*r[2] < 0) k = -k; + double wnorm = sqrt( 2*rnormsq + 2*k*r[2] ); + w[0] = r[0]/wnorm; + w[1] = r[1]/wnorm; + w[2] = (r[2] + k)/wnorm; + + // matrix transformation using Householder matrix, the resulting + // matrix is zero on third row and column + // [q0,q1,q2]^t = A w + // alpha = w^t A w + double q0 = a*w[0] + c*w[1]/2 + d*w[2]/2; + double q1 = b*w[1] + c*w[0]/2 + e*w[2]/2; + double alpha = a*w[0]*w[0] + b*w[1]*w[1] + c*w[0]*w[1] + + d*w[0]*w[2] + e*w[1]*w[2] + f*w[2]*w[2]; + double a00 = a - 4*w[0]*q0 + 4*w[0]*w[0]*alpha; + double a11 = b - 4*w[1]*q1 + 4*w[1]*w[1]*alpha; + double a01 = c/2 - 2*w[1]*q0 - 2*w[0]*q1 + 4*w[0]*w[1]*alpha; + + double dis = a01*a01 - a00*a11; + assert ( dis >= 0 ); + double sqrtdis = sqrt( dis ); + double px, py; + if ( which*a01 > 0 ) + { + px = a01 + which*sqrtdis; + py = a11; + } else { + px = a00; + py = a01 - which*sqrtdis; + } + double p[3]; // vector orthogonal to one of the two planes + double pscalw = w[0]*px + w[1]*py; + p[0] = px - 2*pscalw*w[0]; + p[1] = py - 2*pscalw*w[1]; + p[2] = - 2*pscalw*w[2]; + + // "r" is the solution of the equation A*(x,y,z) = (0,0,0) where + // A is the matrix of the degenerate conic. This is what we + // called in the conic theory we saw in high school a "double + // point". It has the unique property that any line going through + // it is a "polar line" of the conic at hand. It only exists for + // degenerate conics. It has another unique property that if you + // take any other point on the conic, then the line between it and + // the double point is part of the conic. + // this is what we use here: we find the double point ( ret.a + // ), and then find another points on the conic. + + ret.a = -p[2]/(p[0]*p[0] + p[1]*p[1]) * Coordinate (p[0],p[1]); + ret.b = ret.a + Coordinate (-p[1],p[0]); + valid = true; + + return ret; +} + +const ConicCartesianData calcConicTransformation ( + const ConicCartesianData& data, const Transformation& t, bool& valid ) +{ + double a[3][3]; + double b[3][3]; + + a[1][1] = data.coeffs[0]; + a[2][2] = data.coeffs[1]; + a[1][2] = a[2][1] = data.coeffs[2]/2; + a[0][1] = a[1][0] = data.coeffs[3]/2; + a[0][2] = a[2][0] = data.coeffs[4]/2; + a[0][0] = data.coeffs[5]; + + Transformation ti = t.inverse( valid ); + if ( ! valid ) return ConicCartesianData(); + + double supnorm = 0.0; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + b[i][j] = 0.; + for (int ii = 0; ii < 3; ii++) + { + for (int jj = 0; jj < 3; jj++) + { + b[i][j] += a[ii][jj]*ti.data( ii, i )*ti.data( jj, j ); + } + } + if ( std::fabs( b[i][j] ) > supnorm ) supnorm = std::fabs( b[i][j] ); + } + } + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + b[i][j] /= supnorm; + } + } + + return ConicCartesianData ( b[1][1], b[2][2], b[1][2] + b[2][1], + b[0][1] + b[1][0], b[0][2] + b[2][0], b[0][0] ); +} + +ConicCartesianData::ConicCartesianData() +{ +} + +bool operator==( const ConicPolarData& lhs, const ConicPolarData& rhs ) +{ + return lhs.focus1 == rhs.focus1 && + lhs.pdimen == rhs.pdimen && + lhs.ecostheta0 == rhs.ecostheta0 && + lhs.esintheta0 == rhs.esintheta0; +} + +ConicCartesianData ConicCartesianData::invalidData() +{ + ConicCartesianData ret; + ret.coeffs[0] = double_inf; + return ret; +} + +bool ConicCartesianData::valid() const +{ + return finite( coeffs[0] ); +} + diff --git a/kig/misc/conic-common.h b/kig/misc/conic-common.h new file mode 100644 index 00000000..bcad5b6b --- /dev/null +++ b/kig/misc/conic-common.h @@ -0,0 +1,278 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + + 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 +**/ + +#ifndef KIG_MISC_CONIC_COMMON_H +#define KIG_MISC_CONIC_COMMON_H + +#include "coordinate.h" +#include <vector> +#include "kignumerics.h" + +class ConicPolarData; +class Transformation; +class LineData; + +/** + * Cartesian Conic Data. This class represents an equation of a conic + * in the form "ax^2 + by^2 + cxy + dx + ey + f = 0". + * \internal The coefficients are stored in the order a - f. + */ +class ConicCartesianData +{ +public: + double coeffs[6]; + ConicCartesianData(); + /** + * Construct a ConicCartesianData from a ConicPolarData. + * Construct a ConicCartesianData that is the cartesian + * representation of the conic represented by d. + */ + explicit ConicCartesianData( const ConicPolarData& d ); + /** + * Construct a ConicCartesianData from its coefficients + * Construct a ConicCartesianData using the coefficients a through f + * from the equation "ax^2 + by^2 + cxy + dx + ey + f = 0" + */ + ConicCartesianData( double a, double b, double c, + double d, double e, double f ) + { + coeffs[0] = a; + coeffs[1] = b; + coeffs[2] = c; + coeffs[3] = d; + coeffs[4] = e; + coeffs[5] = f; + } + ConicCartesianData( const double incoeffs[6] ); + + /** + * Invalid conic. + * Return a ConicCartesianData representing an invalid conic. + * \see valid() + */ + static ConicCartesianData invalidData(); + /** + * Test validity. + * Return whether this is a valid conic. + * \see invalidData() + */ + bool valid() const; +}; + +/** + * This class represents an equation of a conic in the form + * \f$ \rho(\theta) = \frac{p}{1 - e \cos\theta}\f$. focus and the + * ecostheta stuff represent the coordinate system in which the + * equation yields the good result.. + */ +class ConicPolarData +{ +public: + /** + * Construct a ConicPolarData from a ConicCartesianData. + * + * Construct a ConicPolarData that is the polar + * representation of the conic represented by d. + */ + explicit ConicPolarData( const ConicCartesianData& data ); + explicit ConicPolarData(); + /** + * Construct a ConicPolarData using the parameters from the equation + * \f$ \rho(\theta) = \frac{p}{1 - e \cos\theta}\f$ + */ + ConicPolarData( const Coordinate& focus1, double dimen, + double ecostheta0, double esintheta0 ); + + /** + * The first focus of this conic. + */ + Coordinate focus1; + /** + * The pdimen value from the polar equation. + */ + double pdimen; + /** + * The ecostheta0 value from the polar equation. + */ + double ecostheta0; + /** + * The esintheta0 value from the polar equation. + */ + double esintheta0; +}; + +bool operator==( const ConicPolarData& lhs, const ConicPolarData& rhs ); + +/** + * These are the constraint values that can be passed to the + * calcConicThroughPoints function. Their meaning is as follows: + * noconstraint: no additional points will be calculated. + * zerotilt: force the symmetry axes to be parallel to the coordinate + * system ( zero tilt ). + * parabolaifzt: the returned conic should be a parabola ( if used in + * combination with zerotilt ) + * circleifzt: the returned conic should be a circle ( if used in + * combination with zerotilt ) + * equilateral: the returned conic should be equilateral + * ysymmetry: the returned conic should be symmetric over the Y-axis. + * xsymmetry: the returned conic should be symmetric over the X-axis. + */ +enum LinearConstraints { + noconstraint, zerotilt, parabolaifzt, circleifzt, + equilateral, ysymmetry, xsymmetry +}; + +/** + * Calculate a conic through a given set of points. points should + * contain at least one, and at most five points. If there are five + * points, then the conic is completely defined. If there are less, + * then additional points will be calculated according to the + * constraints given. See above for the various constraints. + * + * An invalid ConicCartesianData is returned if there is no conic + * through the given set of points, or if not enough constraints are + * given for a conic to be calculated. + */ +const ConicCartesianData calcConicThroughPoints ( + const std::vector<Coordinate>& points, + const LinearConstraints c1 = noconstraint, + const LinearConstraints c2 = noconstraint, + const LinearConstraints c3 = noconstraint, + const LinearConstraints c4 = noconstraint, + const LinearConstraints c5 = noconstraint); + +/** + * This function is used by ConicBFFP. It calcs the polar equation + * for a hyperbola ( type == -1 ) or ellipse ( type == 1 ) with + * focuses args[0] and args[1], and with args[2] on the edge of the + * conic. args.size() should be two or three. If it is two, the two + * points are taken to be the focuses, and a third point is chosen in + * a sensible way.. + */ +const ConicPolarData calcConicBFFP( + const std::vector<Coordinate>& args, + int type ); + +/** + * function used by ConicBDFP. It calcs the conic with directrix d, + * focus f, and point p on the conic.. + */ +const ConicPolarData calcConicBDFP( + const LineData& d, const Coordinate& f, const Coordinate& p ); + +/** + * This calcs the hyperbola defined by its two asymptotes line1 and + * line2, and a point p on the edge. + */ +const ConicCartesianData calcConicByAsymptotes( + const LineData& line1, + const LineData& line2, + const Coordinate& p ); + +/** + * This function calculates the polar line of the point cpole with + * respect to the given conic data. As the last argument, you should + * pass a reference to a boolean. This boolean will be set to true if + * the returned LineData is valid, and to false if the returned line + * is not valid. The latter condition only occurs if a "line at + * infinity" would have had to be returned. + */ +const LineData calcConicPolarLine ( + const ConicCartesianData& data, + const Coordinate& cpole, + bool& valid ); + +/** + * This function calculates the polar point of the line polar with + * respect to the given conic data. As the last argument, you should + * pass a reference to a boolean. This boolean will be set to true if + * the returned LineData is valid, and to false if the returned line + * is not valid. The latter condition only occurs if a "point at + * infinity" would have had to be returned. + */ +const Coordinate calcConicPolarPoint ( + const ConicCartesianData& data, + const LineData& polar ); + +/** + * This function calculates the intersection of a given line ( l ) and + * a given conic ( c ). A line and a conic have two intersections in + * general, and as such, which should be set to -1 or 1 depending on + * which intersection you want. As the last argument, you should pass + * a reference to a boolean. This boolean will be set to true if the + * returned point is valid, and to false if the returned point is not + * valid. The latter condition only occurs if the given conic and + * line do not have the specified intersection. + * + * knownparam is something special: If you already know one + * intersection of the line and the conic, and you want the other one, + * then you should set which to 0, knownparam to the curve parameter + * of the point you already know ( i.e. the value returned by + * conicimp->getParam( otherpoint ) ). + */ +const Coordinate calcConicLineIntersect( const ConicCartesianData& c, + const LineData& l, + double knownparam, + int which ); + +/** + * This function calculates the asymptote of the given conic ( data ). + * A conic has two asymptotes in general, so which should be set to +1 + * or -1 depending on which asymptote you want. As the last argument, + * you should pass a reference to a boolean. This boolean will be set + * to true if the returned line is valid, and to false if the returned + * line is not valid. The latter condition only occurs if the given + * conic does not have the specified asymptote. + */ +const LineData calcConicAsymptote( + const ConicCartesianData data, + int which, bool &valid ); + +/** + * This function calculates the radical line of two conics. A radical + * line is the line that goes through two of the intersections of two + * conics. Since two conics have up to four intersections in general, + * there are three sets of two radical lines. zeroindex specifies + * which set of radical lines you want ( set it to 1, 2 or 3 ), and + * which is set to -1 or +1 depending on which of the two radical + * lines in the set you want. As the last argument, you should pass a + * reference to a boolean. This boolean will be set to true if the + * returned line is valid, and to false if the returned line is not + * valid. The latter condition only occurs if the given conics do not + * have the specified radical line. + */ +const LineData calcConicRadical( const ConicCartesianData& cequation1, + const ConicCartesianData& cequation2, + int which, int zeroindex, bool& valid ); + +/** + * This calculates the image of the given conic ( data ) through the + * given transformation ( t ). As the last argument, you should pass + * a reference to a boolean. This boolean will be set to true if the + * returned line is valid, and to false if the returned line is not + * valid. The latter condition only occurs if the given + * transformation is singular, and as such, the transformation of the + * conic cannot be calculated. + */ +const ConicCartesianData calcConicTransformation ( + const ConicCartesianData& data, + const Transformation& t, bool& valid ); + +#endif // KIG_MISC_CONIC_COMMON_H diff --git a/kig/misc/coordinate.cpp b/kig/misc/coordinate.cpp new file mode 100644 index 00000000..13501bc9 --- /dev/null +++ b/kig/misc/coordinate.cpp @@ -0,0 +1,184 @@ +// 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.h" + +#include <qglobal.h> +#include <cmath> +#include <kdebug.h> + +#include "common.h" + +using namespace std; + +Coordinate Coordinate::fromQPoint( const QPoint& p ) +{ + return Coordinate( p.x(), p.y() ); +} + +kdbgstream& operator<<( kdbgstream& s, const Coordinate& t ) +{ + s << "x: " << t.x << " y: " << t.y << endl; + return s; +} + +const Coordinate operator+ ( const Coordinate& a, const Coordinate& b ) +{ + return Coordinate ( a.x + b.x, a.y + b.y ); +} + +const Coordinate operator- ( const Coordinate& a, const Coordinate& b ) +{ + return Coordinate ( a.x - b.x, a.y - b.y ); +} + +const Coordinate operator* ( const Coordinate& a, double r ) +{ + return Coordinate ( r*a.x, r*a.y ); +} + +const Coordinate operator* ( double r, const Coordinate& a ) +{ + return Coordinate ( r*a.x, r*a.y ); +} + +const Coordinate operator/ ( const Coordinate& a, double r ) +{ + return Coordinate ( a.x/r, a.y/r ); +} + +bool operator==( const Coordinate& a, const Coordinate& b ) +{ + return a.x == b.x && a.y == b.y; +} + +bool operator!=( const Coordinate& a, const Coordinate& b ) +{ + return !operator==( a, b ); +} + +Coordinate::Coordinate() + : x(0), + y(0) +{ +} + +Coordinate::Coordinate( double nx, double ny ) + : x( nx ), + y( ny ) +{ +} + +Coordinate::Coordinate( const Coordinate& p ) + : x( p.x ), + y( p.y ) +{ +} + +const Coordinate Coordinate::operator-() const +{ + return Coordinate ( -x, -y ); +} + +Coordinate& Coordinate::operator=( const Coordinate& p ) +{ + x = p.x; + y = p.y; + return *this; +} + +Coordinate& Coordinate::operator+=( const Coordinate& p ) +{ + x += p.x; + y += p.y; + return *this; +} + +Coordinate& Coordinate::operator-=( const Coordinate& p ) +{ + x -= p.x; + y -= p.y; + return *this; +} + +Coordinate& Coordinate::operator*=( double r ) +{ + x *= r; + y *= r; + return *this; +} + +Coordinate& Coordinate::operator*=( int r ) +{ + x *= r; + y *= r; + return *this; +} + +Coordinate& Coordinate::operator/=( double r ) +{ + x /= r; + y /= r; + return *this; +} + +double Coordinate::distance( const Coordinate& p ) const +{ + return (p - *this).length(); +} + +double Coordinate::length() const +{ + return sqrt(x*x+y*y); +} + +const Coordinate Coordinate::orthogonal() const +{ + return Coordinate( -y, x ); +} + +const Coordinate Coordinate::normalize( double l ) const +{ + double oldlength = length(); + return ( *this * l ) / oldlength; +} + +const Coordinate Coordinate::round() const +{ + return Coordinate( qRound( x ), qRound( y ) ); +} + +QPoint Coordinate::toQPoint() const +{ + Coordinate t = round(); + return QPoint( (int) t.x, (int) t.y ); +} + +Coordinate Coordinate::invalidCoord() +{ + return Coordinate( double_inf, double_inf ); +} + +bool Coordinate::valid() const +{ + return abs( x ) != double_inf && abs( y ) != double_inf; +} + +double operator*( const Coordinate& a, const Coordinate& b ) +{ + return a.x * b.x + a.y * b.y; +} diff --git a/kig/misc/coordinate.h b/kig/misc/coordinate.h new file mode 100644 index 00000000..a56edb76 --- /dev/null +++ b/kig/misc/coordinate.h @@ -0,0 +1,169 @@ +/** + 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 +**/ + + +#ifndef KIG_MISC_COORDINATE_H +#define KIG_MISC_COORDINATE_H + +class QPoint; +class kdbgstream; + +/** + * The Coordinate class is the basic class representing a 2D location + * by its x and y components. It has all relevant arithmetic + * operators properly defined, and should be straightforward to use.. + */ +class Coordinate +{ +public: + static Coordinate fromQPoint( const QPoint& p ); + + /** Constructor. Construct a new Coordinate, with a given x and y + * value. + */ + Coordinate( double x, double y ); + /** Copy Constructor. Construct a new Coordinate, and give it the + * same value as p. + */ + Coordinate( const Coordinate& p ); + /** + * \ifnot creating-python-scripting-doc + * \brief Default Constructor + * + * Constructs a new Coordinate, with x and y initialized to 0. + * \endif + */ + Coordinate(); + ~Coordinate() {} + + /** Create an invalid Coordinate. This is a special value of a + * Coordinate that signals that something went wrong.. + * + * \see Coordinate::valid + * + * \internal We represent an invalid coordinate by setting x or y to + * positive or negative infinity. This is handy, since it doesn't + * require us to adapt most of the functions, it doesn't need extra + * space, and most of the times that we should get an invalid coord, + * we get one automatically.. + */ + static Coordinate invalidCoord(); + /** Return whether this is a valid Coordinate. + * \see Coordinate::invalidCoord + */ + bool valid() const; + + /** Distance to another Coordinate. + */ + double distance ( const Coordinate& p ) const; + /** Length. Returns the length or norm of this coordinate. + * I.e. return the distance from this Coordinate to the origin. + * \see squareLength + */ + double length () const; + /** Square length. Equivalent to the square of \ref length, but a + * bit more efficient because no square root has to be calculated. + * \see length + */ + inline double squareLength() const; + /** Inverse. Returns the inverse of this Coordinate. + */ + const Coordinate operator- () const; + /** Orthogonal. Returns a vector which is orthogonal on this vector. + * This relation always holds: + * <pre> + * Coordinate a = ...; + * assert( a*a.orthogonal() ) == 0; + * </pre> + */ + const Coordinate orthogonal() const; + /** Round. Returns this coordinate, rounded to the nearest integral + * values. + */ + const Coordinate round() const; + /** Normalize. This sets the length to length, while keeping the + * x/y ratio untouched... + */ + const Coordinate normalize( double length = 1 ) const; + QPoint toQPoint() const; + + Coordinate& operator= ( const Coordinate& c ); + /** Add. Add c to this Coordinate + */ + Coordinate& operator+= ( const Coordinate& c ); + /** Subtract. Subtract c from this Coordinate + */ + Coordinate& operator-= ( const Coordinate& c ); + /** Scale. Scales this Coordinate by a factor r + */ + Coordinate& operator*= ( double r ); + /** Scale. Scales this Coordinate by a factor r + */ + Coordinate& operator*= ( int r ); + /** Scale. Scales this Coordinate by a factor 1/r + */ + Coordinate& operator/= ( double r ); +public: + /** X Component. The X Component of this Coordinate. + */ + double x; + /** Y Component. The Y Component of this Coordinate. + */ + double y; + + friend kdbgstream& operator<<( kdbgstream& s, const Coordinate& t ); + /** Add. Returns the sum of a and b. + */ + friend const Coordinate operator+ ( const Coordinate& a, const Coordinate& b ); + /** Subtract. Returns the difference between a and b. + */ + friend const Coordinate operator- ( const Coordinate& a, const Coordinate& b ); + /** Scale. Returns this a, scaled by a factor of r. + */ + friend const Coordinate operator* ( const Coordinate& a, double r ); + /** Scale. Returns a, scaled by a factor of 1/r. + */ + friend const Coordinate operator/ ( const Coordinate& a, double r ); + /** Scalar Product. Returns the scalar product of a and b. + */ + friend double operator*( const Coordinate& a, const Coordinate& b ); + /** Equal. Tests two Coordinates for equality. + */ + friend bool operator==( const Coordinate&, const Coordinate& ); + /** Not Equal. Tests two Coordinates for inequality. + */ + friend bool operator!=( const Coordinate&, const Coordinate& ); +}; + +const Coordinate operator/ ( const Coordinate& a, double r ); +kdbgstream& operator<<( kdbgstream& s, const Coordinate& t ); +const Coordinate operator+ ( const Coordinate& a, const Coordinate& b ); +const Coordinate operator- ( const Coordinate& a, const Coordinate& b ); +const Coordinate operator* ( const Coordinate& a, double r ); +const Coordinate operator* ( double r, const Coordinate& a ); +double operator*( const Coordinate& a, const Coordinate& b ); + +double Coordinate::squareLength() const +{ + return x*x+y*y; +} + +#endif + diff --git a/kig/misc/coordinate_system.cpp b/kig/misc/coordinate_system.cpp new file mode 100644 index 00000000..dd5181c2 --- /dev/null +++ b/kig/misc/coordinate_system.cpp @@ -0,0 +1,720 @@ +/** + 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 <qpainter.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> +#include <knumvalidator.h> + +#include <string> +#include <math.h> + +class CoordinateValidator + : public QValidator +{ + bool mpolar; +#ifdef KIG_USE_KDOUBLEVALIDATOR + KDoubleValidator mdv; +#else + KFloatValidator mdv; +#endif + mutable QRegExp mre; +public: + CoordinateValidator( bool polar ); + ~CoordinateValidator(); + State validate ( QString & input, int & pos ) const; + void fixup ( QString & input ) const; +}; + + +CoordinateValidator::CoordinateValidator( bool polar ) + : QValidator( 0, 0 ), mpolar( polar ), mdv( 0, 0 ), + mre( polar ? "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?°? ?\\)?" + : "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?\\)?" ) +{ +} + +CoordinateValidator::~CoordinateValidator() +{ +} + +QValidator::State CoordinateValidator::validate( QString & input, int & pos ) const +{ + QString 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.find( ';' ); + if ( scp == -1 ) return mdv.validate( tinput, pos ) == Invalid ? Invalid : Valid; + else + { + QString p1 = tinput.left( scp ); + QString 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( QString & input ) const +{ + int nsc = input.contains( ';' ); + if ( nsc > 1 ) + { + // where is the second ';' + int i = input.find( ';' ); + i = input.find( ';', i ); + input = input.left( i ); + }; + // now the string has at most one semicolon left.. + int sc = input.find( ';' ); + if ( sc == -1 ) + { + sc = input.length(); + KLocale* l = KGlobal::locale(); + if ( mpolar ) + input.append( QString::fromLatin1( ";" ) + l->positiveSign() + + QString::fromLatin1( "0°" ) ); + else + input.append( QString::fromLatin1( ";" ) + l->positiveSign() + + QString::fromLatin1( "0" ) + l->decimalSymbol() + + QString::fromLatin1( "0" ) ); + }; + mre.exactMatch( input ); + QString ds1 = mre.cap( 1 ); + mdv.fixup( ds1 ); + QString ds2 = mre.cap( 2 ); + mdv.fixup( ds2 ); + input = ds1 + QString::fromLatin1( "; " ) + ds2; +} + +EuclideanCoords::EuclideanCoords() +{ +} + +QString 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 ) ) ); + QString xs = KGlobal::locale()->formatNumber( p.x, l ); + QString ys = KGlobal::locale()->formatNumber( p.y, l ); + return QString::fromLatin1( "( %1; %2 )" ).arg( xs ).arg( ys ); +} + +Coordinate EuclideanCoords::toScreen(const QString& s, bool& ok) const +{ + QRegExp r( "\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?\\)?" ); + ok = ( r.search(s) == 0 ); + if (ok) + { + QString xs = r.cap(1); + QString 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( QPen( 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( QPen( Qt::gray, 1, Qt::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( QPen( Qt::gray, 1, Qt::SolidLine ) ); + p.setBrush( QBrush( Qt::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 ) +} + +QString 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." ); +} + +QString 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() +{ +} + +QString 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 ); + + QString rs = KGlobal::locale()->formatNumber( r, l ); + QString ts = KGlobal::locale()->formatNumber( theta, 0 ); + + return QString::fromLatin1("( %1; %2° )").arg( rs ).arg( ts ); +} + +QString 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." ); +} + +QString 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 QString& s, bool& ok) const +{ + QRegExp regexp("\\(? ?([0-9.,+-]+); ?([0-9.,+-]+) ?°? ?\\)?" ); + ok = ( regexp.search( s ) == 0 ); + if (ok) + { + QString rs = regexp.cap( 1 ); + double r = KGlobal::locale()->readNumber( rs, &ok ); + if ( ! ok ) r = rs.toDouble( &ok ); + if ( ! ok ) return Coordinate(); + QString 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( QPen( lightGray, 0, DotLine ) ); + for ( double i = begin; i <= end + d / 2; i += d ) + drawGridLine( p, c, fabs( i ) ); + } + + /****** the axes ******/ + if ( showaxes ) + { + p.setPen( QPen( Qt::gray, 1, Qt::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; + + QString 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; + + QString 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( QPen( Qt::gray, 1, Qt::SolidLine ) ); + p.setBrush( QBrush( Qt::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 ) +} + +QValidator* EuclideanCoords::coordinateValidator() const +{ + return new CoordinateValidator( false ); +} + +QValidator* PolarCoords::coordinateValidator() const +{ + return new CoordinateValidator( true ); +} + +QStringList CoordinateSystemFactory::names() +{ + QStringList 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; +} + +QString 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 QString::null; + } +} + +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 = qRound( ( c.x - hgraphmin ) / hd ) * hd + hgraphmin; + const double ny = qRound( ( 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 find + + // 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 = qRound( 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 ); +} diff --git a/kig/misc/coordinate_system.h b/kig/misc/coordinate_system.h new file mode 100644 index 00000000..af426909 --- /dev/null +++ b/kig/misc/coordinate_system.h @@ -0,0 +1,134 @@ +/* + 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 +*/ + +#ifndef KIG_MISC_COORDINATE_SYSTEM_H +#define KIG_MISC_COORDINATE_SYSTEM_H + +#include <qnamespace.h> + +class KigPainter; +class KigDocument; +class KigWidget; +class CoordinateSystem; +class QValidator; +class Coordinate; +class QString; +class QStringList; +class QWidget; + +/** + * a factory to build a CoordinateSystem and a small handle to the + * existant CoordinateSystem's... + */ +class CoordinateSystemFactory +{ +public: + enum { Euclidean = 0, Polar = 1 }; + + static QStringList names(); + static QString setCoordinateSystemStatement( int id ); + static CoordinateSystem* build( int which ); + static CoordinateSystem* build( const char* type ); +}; + +/** + * a CoordinateSystem is what the user sees: it is kept by KigPart to + * show the user a grid, and to show the coordinates of points... it + * allows for weird CoordinateSystem's like homogeneous or + * projective... + * internally, it does nothing, it could almost have been an ordinary + * Object..., mapping coordinates from and to the screen to and from + * the internal coordinates is done elsewhere ( KigPainter and + * KigWidget... ) + */ +class CoordinateSystem + : public Qt +{ +public: + CoordinateSystem(); + virtual ~CoordinateSystem(); + + virtual QString fromScreen ( const Coordinate& pt, const KigDocument& w ) const = 0; + /** + * This returns a notice to say in which format coordinates should + * be entered. This should be something like: + * i18n( "Enter coordinates in the following form: \"(x,y)\", where + * x is the x coordinate, and y is the y coordinate." ); + */ + virtual QString coordinateFormatNotice() const = 0; + /** + * Like \ref coordinateFormatNotice(), but with HTML tags useful to + * have a rich text... + */ + virtual QString coordinateFormatNoticeMarkup() const = 0; + virtual Coordinate toScreen (const QString& pt, bool& ok) const = 0; + virtual void drawGrid ( KigPainter& p, bool showgrid = true, + bool showaxes = true ) const = 0; + virtual QValidator* coordinateValidator() const = 0; + virtual Coordinate snapToGrid( const Coordinate& c, + const KigWidget& w ) const = 0; + + virtual const char* type() const = 0; + virtual int id() const = 0; +}; + +class EuclideanCoords + : public CoordinateSystem +{ +public: + EuclideanCoords(); + ~EuclideanCoords(); + QString fromScreen( const Coordinate& pt, const KigDocument& w ) const; + QString coordinateFormatNotice() const; + QString coordinateFormatNoticeMarkup() const; + Coordinate toScreen (const QString& pt, bool& ok) const; + void drawGrid ( KigPainter& p, bool showgrid = true, + bool showaxes = true ) const; + QValidator* coordinateValidator() const; + Coordinate snapToGrid( const Coordinate& c, + const KigWidget& w ) const; + + const char* type() const; + int id() const; +}; + +class PolarCoords + : public CoordinateSystem +{ + void drawGridLine( KigPainter& p, const Coordinate& center, + double radius ) const; +public: + PolarCoords(); + ~PolarCoords(); + QString fromScreen( const Coordinate& pt, const KigDocument& w ) const; + QString coordinateFormatNotice() const; + QString coordinateFormatNoticeMarkup() const; + Coordinate toScreen (const QString& pt, bool& ok) const; + void drawGrid ( KigPainter& p, bool showgrid = true, + bool showaxes = true ) const; + QValidator* coordinateValidator() const; + Coordinate snapToGrid( const Coordinate& c, + const KigWidget& w ) const; + + const char* type() const; + int id() const; +}; + +#endif diff --git a/kig/misc/cubic-common.cc b/kig/misc/cubic-common.cc new file mode 100644 index 00000000..029f1194 --- /dev/null +++ b/kig/misc/cubic-common.cc @@ -0,0 +1,527 @@ +// Copyright (C) 2003 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 <config.h> + +#include "cubic-common.h" +#include "kignumerics.h" +#include "kigtransform.h" + +#ifdef HAVE_IEEEFP_H +#include <ieeefp.h> +#endif + +/* + * coefficients of the cartesian equation for cubics + */ + +CubicCartesianData::CubicCartesianData() +{ + std::fill( coeffs, coeffs + 10, 0 ); +} + +CubicCartesianData::CubicCartesianData( + const double incoeffs[10] ) +{ + std::copy( incoeffs, incoeffs + 10, coeffs ); +} + +const CubicCartesianData calcCubicThroughPoints ( + const std::vector<Coordinate>& points ) +{ + // points is a vector of at most 9 points through which the cubic is + // constrained. + // this routine should compute the coefficients in the cartesian equation + // they are defined up to a multiplicative factor. + // since we don't know (in advance) which one of them is nonzero, we + // simply keep all 10 parameters, obtaining a 9x10 linear system which + // we solve using gaussian elimination with complete pivoting + // If there are too few, then we choose some cool way to fill in the + // empty parts in the matrix according to the LinearConstraints + // given.. + + // 9 rows, 10 columns.. + double row0[10]; + double row1[10]; + double row2[10]; + double row3[10]; + double row4[10]; + double row5[10]; + double row6[10]; + double row7[10]; + double row8[10]; + double *matrix[9] = {row0, row1, row2, row3, row4, row5, row6, row7, row8}; + double solution[10]; + int scambio[10]; + + int numpoints = points.size(); + int numconstraints = 9; + + // fill in the matrix elements + for ( int i = 0; i < numpoints; ++i ) + { + double xi = points[i].x; + double yi = points[i].y; + matrix[i][0] = 1.0; + matrix[i][1] = xi; + matrix[i][2] = yi; + matrix[i][3] = xi*xi; + matrix[i][4] = xi*yi; + matrix[i][5] = yi*yi; + matrix[i][6] = xi*xi*xi; + matrix[i][7] = xi*xi*yi; + matrix[i][8] = xi*yi*yi; + matrix[i][9] = yi*yi*yi; + } + + for ( int i = 0; i < numconstraints; i++ ) + { + if (numpoints >= 9) break; // don't add constraints if we have enough + for (int j = 0; j < 10; ++j) matrix[numpoints][j] = 0.0; + bool addedconstraint = true; + switch (i) + { + case 0: + matrix[numpoints][7] = 1.0; + matrix[numpoints][8] = -1.0; + break; + case 1: + matrix[numpoints][7] = 1.0; + break; + case 2: + matrix[numpoints][9] = 1.0; + break; + case 3: + matrix[numpoints][4] = 1.0; + break; + case 4: + matrix[numpoints][5] = 1.0; + break; + case 5: + matrix[numpoints][3] = 1.0; + break; + case 6: + matrix[numpoints][1] = 1.0; + break; + + default: + addedconstraint = false; + break; + } + + if (addedconstraint) ++numpoints; + } + + if ( ! GaussianElimination( matrix, numpoints, 10, scambio ) ) + return CubicCartesianData::invalidData(); + // fine della fase di eliminazione + BackwardSubstitution( matrix, numpoints, 10, scambio, solution ); + + // now solution should contain the correct coefficients.. + return CubicCartesianData( solution ); +} + +const CubicCartesianData calcCubicCuspThroughPoints ( + const std::vector<Coordinate>& points ) +{ + // points is a vector of at most 4 points through which the cubic is + // constrained. Moreover the cubic is required to have a cusp at the + // origin. + + // 9 rows, 10 columns.. + double row0[10]; + double row1[10]; + double row2[10]; + double row3[10]; + double row4[10]; + double row5[10]; + double row6[10]; + double row7[10]; + double row8[10]; + double *matrix[9] = {row0, row1, row2, row3, row4, row5, row6, row7, row8}; + double solution[10]; + int scambio[10]; + + int numpoints = points.size(); + int numconstraints = 9; + + // fill in the matrix elements + for ( int i = 0; i < numpoints; ++i ) + { + double xi = points[i].x; + double yi = points[i].y; + matrix[i][0] = 1.0; + matrix[i][1] = xi; + matrix[i][2] = yi; + matrix[i][3] = xi*xi; + matrix[i][4] = xi*yi; + matrix[i][5] = yi*yi; + matrix[i][6] = xi*xi*xi; + matrix[i][7] = xi*xi*yi; + matrix[i][8] = xi*yi*yi; + matrix[i][9] = yi*yi*yi; + } + + for ( int i = 0; i < numconstraints; i++ ) + { + if (numpoints >= 9) break; // don't add constraints if we have enough + for (int j = 0; j < 10; ++j) matrix[numpoints][j] = 0.0; + bool addedconstraint = true; + switch (i) + { + case 0: + matrix[numpoints][0] = 1.0; // through the origin + break; + case 1: + matrix[numpoints][1] = 1.0; + break; + case 2: + matrix[numpoints][2] = 1.0; // no first degree term + break; + case 3: + matrix[numpoints][3] = 1.0; // a011 (x^2 coeff) = 0 + break; + case 4: + matrix[numpoints][4] = 1.0; // a012 (xy coeff) = 0 + break; + case 5: + matrix[numpoints][7] = 1.0; + matrix[numpoints][8] = -1.0; + break; + case 6: + matrix[numpoints][7] = 1.0; + break; + case 7: + matrix[numpoints][9] = 1.0; + break; + case 8: + matrix[numpoints][6] = 1.0; + break; + + default: + addedconstraint = false; + break; + } + + if (addedconstraint) ++numpoints; + } + + if ( ! GaussianElimination( matrix, numpoints, 10, scambio ) ) + return CubicCartesianData::invalidData(); + // fine della fase di eliminazione + BackwardSubstitution( matrix, numpoints, 10, scambio, solution ); + + // now solution should contain the correct coefficients.. + return CubicCartesianData( solution ); +} + +const CubicCartesianData calcCubicNodeThroughPoints ( + const std::vector<Coordinate>& points ) +{ + // points is a vector of at most 6 points through which the cubic is + // constrained. Moreover the cubic is required to have a node at the + // origin. + + // 9 rows, 10 columns.. + double row0[10]; + double row1[10]; + double row2[10]; + double row3[10]; + double row4[10]; + double row5[10]; + double row6[10]; + double row7[10]; + double row8[10]; + double *matrix[9] = {row0, row1, row2, row3, row4, row5, row6, row7, row8}; + double solution[10]; + int scambio[10]; + + int numpoints = points.size(); + int numconstraints = 9; + + // fill in the matrix elements + for ( int i = 0; i < numpoints; ++i ) + { + double xi = points[i].x; + double yi = points[i].y; + matrix[i][0] = 1.0; + matrix[i][1] = xi; + matrix[i][2] = yi; + matrix[i][3] = xi*xi; + matrix[i][4] = xi*yi; + matrix[i][5] = yi*yi; + matrix[i][6] = xi*xi*xi; + matrix[i][7] = xi*xi*yi; + matrix[i][8] = xi*yi*yi; + matrix[i][9] = yi*yi*yi; + } + + for ( int i = 0; i < numconstraints; i++ ) + { + if (numpoints >= 9) break; // don't add constraints if we have enough + for (int j = 0; j < 10; ++j) matrix[numpoints][j] = 0.0; + bool addedconstraint = true; + switch (i) + { + case 0: + matrix[numpoints][0] = 1.0; + break; + case 1: + matrix[numpoints][1] = 1.0; + break; + case 2: + matrix[numpoints][2] = 1.0; + break; + case 3: + matrix[numpoints][7] = 1.0; + matrix[numpoints][8] = -1.0; + break; + case 4: + matrix[numpoints][7] = 1.0; + break; + case 5: + matrix[numpoints][9] = 1.0; + break; + case 6: + matrix[numpoints][4] = 1.0; + break; + case 7: + matrix[numpoints][5] = 1.0; + break; + case 8: + matrix[numpoints][3] = 1.0; + break; + + default: + addedconstraint = false; + break; + } + + if (addedconstraint) ++numpoints; + } + + if ( ! GaussianElimination( matrix, numpoints, 10, scambio ) ) + return CubicCartesianData::invalidData(); + // fine della fase di eliminazione + BackwardSubstitution( matrix, numpoints, 10, scambio, solution ); + + // now solution should contain the correct coefficients.. + return CubicCartesianData( solution ); +} + +/* + * computation of the y value corresponding to some x value + */ + +double calcCubicYvalue ( double x, double ymin, double ymax, int root, + CubicCartesianData data, bool& valid, + int &numroots ) +{ + valid = true; + + // compute the third degree polinomial: + double a000 = data.coeffs[0]; + double a001 = data.coeffs[1]; + double a002 = data.coeffs[2]; + double a011 = data.coeffs[3]; + double a012 = data.coeffs[4]; + double a022 = data.coeffs[5]; + double a111 = data.coeffs[6]; + double a112 = data.coeffs[7]; + double a122 = data.coeffs[8]; + double a222 = data.coeffs[9]; + + // first the y^3 coefficient, it coming only from a222: + double a = a222; + // next the y^2 coefficient (from a122 and a022): + double b = a122*x + a022; + // next the y coefficient (from a112, a012 and a002): + double c = a112*x*x + a012*x + a002; + // finally the constant coefficient (from a111, a011, a001 and a000): + double d = a111*x*x*x + a011*x*x + a001*x + a000; + + return calcCubicRoot ( ymin, ymax, a, b, c, d, root, valid, numroots ); +} + +const Coordinate calcCubicLineIntersect( const CubicCartesianData& cu, + const LineData& l, + int root, bool& valid ) +{ + assert( root == 1 || root == 2 || root == 3 ); + + double a, b, c, d; + calcCubicLineRestriction ( cu, l.a, l.b-l.a, a, b, c, d ); + int numroots; + double param = + calcCubicRoot ( -1e10, 1e10, a, b, c, d, root, valid, numroots ); + return l.a + param*(l.b - l.a); +} + +/* + * calculate the cubic polynomial resulting from the restriction + * of a cubic to a line (defined by two "Coordinates": a point and a + * direction) + */ + +void calcCubicLineRestriction ( CubicCartesianData data, + Coordinate p, Coordinate v, + double& a, double& b, double& c, double& d ) +{ + a = b = c = d = 0; + + double a000 = data.coeffs[0]; + double a001 = data.coeffs[1]; + double a002 = data.coeffs[2]; + double a011 = data.coeffs[3]; + double a012 = data.coeffs[4]; + double a022 = data.coeffs[5]; + double a111 = data.coeffs[6]; + double a112 = data.coeffs[7]; + double a122 = data.coeffs[8]; + double a222 = data.coeffs[9]; + + // zero degree term + d += a000; + + // first degree terms + d += a001*p.x + a002*p.y; + c += a001*v.x + a002*v.y; + + // second degree terms + d += a011*p.x*p.x + a012*p.x*p.y + a022*p.y*p.y; + c += 2*a011*p.x*v.x + a012*(p.x*v.y + v.x*p.y) + 2*a022*p.y*v.y; + b += a011*v.x*v.x + a012*v.x*v.y + a022*v.y*v.y; + + // third degree terms: a111 x^3 + a222 y^3 + d += a111*p.x*p.x*p.x + a222*p.y*p.y*p.y; + c += 3*(a111*p.x*p.x*v.x + a222*p.y*p.y*v.y); + b += 3*(a111*p.x*v.x*v.x + a222*p.y*v.y*v.y); + a += a111*v.x*v.x*v.x + a222*v.y*v.y*v.y; + + // third degree terms: a112 x^2 y + a122 x y^2 + d += a112*p.x*p.x*p.y + a122*p.x*p.y*p.y; + c += a112*(p.x*p.x*v.y + 2*p.x*v.x*p.y) + a122*(v.x*p.y*p.y + 2*p.x*p.y*v.y); + b += a112*(v.x*v.x*p.y + 2*v.x*p.x*v.y) + a122*(p.x*v.y*v.y + 2*v.x*v.y*p.y); + a += a112*v.x*v.x*v.y + a122*v.x*v.y*v.y; +} + + +const CubicCartesianData calcCubicTransformation ( + const CubicCartesianData& data, + const Transformation& t, bool& valid ) +{ + double a[3][3][3]; + double b[3][3][3]; + CubicCartesianData dataout; + + int icount = 0; + for (int i=0; i < 3; i++) + { + for (int j=i; j < 3; j++) + { + for (int k=j; k < 3; k++) + { + a[i][j][k] = data.coeffs[icount++]; + if ( i < k ) + { + if ( i == j ) // case aiik + { + a[i][i][k] /= 3.; + a[i][k][i] = a[k][i][i] = a[i][i][k]; + } + else if ( j == k ) // case aijj + { + a[i][j][j] /= 3.; + a[j][i][j] = a[j][j][i] = a[i][j][j]; + } + else // case aijk (i<j<k) + { + a[i][j][k] /= 6.; + a[i][k][j] = a[j][i][k] = a[j][k][i] = + a[k][i][j] = a[k][j][i] = a[i][j][k]; + } + } + } + } + } + + Transformation ti = t.inverse( valid ); + if ( ! valid ) return dataout; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + for (int k = 0; k < 3; k++) + { + b[i][j][k] = 0.; + for (int ii = 0; ii < 3; ii++) + { + for (int jj = 0; jj < 3; jj++) + { + for (int kk = 0; kk < 3; kk++) + { + b[i][j][k] += a[ii][jj][kk]*ti.data( ii, i )*ti.data( jj, j )*ti.data( kk, k ); + } + } + } + } + } + } + +// assert (fabs(b[0][1][2] - b[1][2][0]) < 1e-8); // test a couple of cases +// assert (fabs(b[0][1][1] - b[1][1][0]) < 1e-8); + + // apparently, the above assertions are wrong ( due to rounding + // errors, Maurizio and I hope :) ), so since the symmetry is not + // present, we just take the sum of the parts of the matrix elements + // that should be symmetric, instead of taking one of them, and + // multiplying it.. + + dataout.coeffs[0] = b[0][0][0]; + dataout.coeffs[1] = b[0][0][1] + b[0][1][0] + b[1][0][0]; + dataout.coeffs[2] = b[0][0][2] + b[0][2][0] + b[2][0][0]; + dataout.coeffs[3] = b[0][1][1] + b[1][0][1] + b[1][1][0]; + dataout.coeffs[4] = b[0][1][2] + b[0][2][1] + b[1][2][0] + b[1][0][2] + b[2][1][0] + b[2][0][1]; + dataout.coeffs[5] = b[0][2][2] + b[2][0][2] + b[2][2][0]; + dataout.coeffs[6] = b[1][1][1]; + dataout.coeffs[7] = b[1][1][2] + b[1][2][1] + b[2][1][1]; + dataout.coeffs[8] = b[1][2][2] + b[2][1][2] + b[2][2][1]; + dataout.coeffs[9] = b[2][2][2]; + + return dataout; +} + +bool operator==( const CubicCartesianData& lhs, const CubicCartesianData& rhs ) +{ + for ( int i = 0; i < 10; ++i ) + if ( lhs.coeffs[i] != rhs.coeffs[i] ) + return false; + return true; +} + +CubicCartesianData CubicCartesianData::invalidData() +{ + CubicCartesianData ret; + ret.coeffs[0] = double_inf; + return ret; +} + +bool CubicCartesianData::valid() const +{ + return finite( coeffs[0] ); +} diff --git a/kig/misc/cubic-common.h b/kig/misc/cubic-common.h new file mode 100644 index 00000000..8fbcd1a2 --- /dev/null +++ b/kig/misc/cubic-common.h @@ -0,0 +1,120 @@ +// Copyright (C) 2003 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. + +#ifndef KIG_MISC_CUBIC_COMMON_H +#define KIG_MISC_CUBIC_COMMON_H + +#include "common.h" + +class Transformation; + +/** + * This class represents an equation of a cubic in the form + * \f$ a_{ijk} x_i x_j x_k = 0 \f$ (in homogeneous coordinates, + * \f$ i,j,k = 0,1,2 \f$), \f$ i <= j <= k \f$. + * The coefficients are stored in lessicografic order. + */ +class CubicCartesianData +{ +public: + double coeffs[10]; + /** + * \ifnot creating-python-scripting-doc + * \brief Default Constructor + * + * Constructs a new CubicCartesianData, with all the coeffs + * initialized to 0. + * \endif + */ + explicit CubicCartesianData(); + /** + * Constructor. Construct a new CubicCartesianData, with the given + * values as coeffs. + */ + CubicCartesianData( double a000, double a001, double a002, + double a011, double a012, double a022, + double a111, double a112, double a122, + double a222 ) + { + coeffs[0] = a000; + coeffs[1] = a001; + coeffs[2] = a002; + coeffs[3] = a011; + coeffs[4] = a012; + coeffs[5] = a022; + coeffs[6] = a111; + coeffs[7] = a112; + coeffs[8] = a122; + coeffs[9] = a222; + } + CubicCartesianData( const double incoeffs[10] ); + + /** + * Create an invalid CubicCartesianData. This is a special state of a + * CubicCartesianData that signals that something went wrong.. + * + * \see CubicCartesianData::valid + * + * \internal We represent an invalid CubicCartesianData by setting all + * the coeffs to positive or negative infinity. This is handy, since + * it doesn't require us to adapt most of the functions, it doesn't + * need extra space, and most of the times that we should get an + * invalid CubicCartesianData, we get one automatically.. + */ + static CubicCartesianData invalidData(); + /** + * Return whether this is a valid CubicCartesianData. + * + * \see CubicCartesianData::invalidData + */ + bool valid() const; +}; + +bool operator==( const CubicCartesianData& lhs, const CubicCartesianData& rhs ); + +/** + * This function calcs a cartesian cubic equation such that the + * given points are on the cubic. There can be at most 9 and at + * least 2 point. If there are less than 9, than the coefficients + * will be chosen to 1.0 if possible + */ +const CubicCartesianData calcCubicThroughPoints ( + const std::vector<Coordinate>& points ); + +const CubicCartesianData calcCubicCuspThroughPoints ( + const std::vector<Coordinate>& points ); + +const CubicCartesianData calcCubicNodeThroughPoints ( + const std::vector<Coordinate>& points ); + +double calcCubicYvalue ( double x, double ymin, double ymax, + int root, CubicCartesianData data, + bool& valid, int& numroots ); + +const Coordinate calcCubicLineIntersect( const CubicCartesianData& c, + const LineData& l, + int root, bool& valid ); + +void calcCubicLineRestriction ( CubicCartesianData data, + Coordinate p1, Coordinate dir, + double& a, double& b, double& c, double& d ); + +const CubicCartesianData calcCubicTransformation ( + const CubicCartesianData& data, + const Transformation& t, bool& valid ); + +#endif diff --git a/kig/misc/goniometry.cc b/kig/misc/goniometry.cc new file mode 100644 index 00000000..13d72fdb --- /dev/null +++ b/kig/misc/goniometry.cc @@ -0,0 +1,137 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2004 Dominique Devriese <devriese@kde.org> + Copyright (C) 2004 Pino Toscano <toscano.pino@tiscali.it> + + 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 "goniometry.h" + +#include <qstringlist.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <cmath> + +Goniometry::Goniometry() +{ + mvalue = 0.0; + msys = Rad; +} + +Goniometry::Goniometry( double value, Goniometry::System system ) +{ + mvalue = value; + msys = system; +} + +Goniometry::~Goniometry() +{ +} + +void Goniometry::setValue( double value ) +{ + mvalue = value; +} + +const double Goniometry::value() const +{ + return mvalue; +} + +void Goniometry::setSystem( Goniometry::System system ) +{ + msys = system; +} + +void Goniometry::convertTo( Goniometry::System system ) +{ + mvalue = convert( mvalue, msys, system ); + msys = system; +} + +const Goniometry::System Goniometry::system() const +{ + return msys; +} + +double Goniometry::getValue( Goniometry::System system ) +{ + return convert( mvalue, msys, system ); +} + +Goniometry& Goniometry::operator=( const Goniometry& g ) +{ + mvalue = g.value(); + msys = g.system(); + return *this; +} + +double Goniometry::convert( const double angle, const Goniometry::System from, const Goniometry::System to ) +{ + switch( from ) + { + case Deg: + { + if ( to == Rad ) + return angle * M_PI / 180; + if ( to == Grad ) + return angle * 10 / 9; + break; + } + case Rad: + { + if ( to == Deg ) + return angle * 180 / M_PI; + if ( to == Grad ) + return angle * 200 / M_PI; + break; + } + case Grad: + { + if ( to == Deg ) + return angle * 9 / 10; + if ( to == Rad ) + return angle * M_PI / 200; + break; + } + } + return angle; +} + +QStringList Goniometry::systemList() +{ + QStringList sl; + sl << i18n( "Translators: Degrees", "Deg" ); + sl << i18n( "Translators: Radians", "Rad" ); + sl << i18n( "Translators: Gradians", "Grad" ); + return sl; +} + +Goniometry::System Goniometry::intToSystem( const int index ) +{ + if( index == 0 ) + return Deg; + else if( index == 1 ) + return Rad; + else if( index == 2 ) + return Grad; + kdDebug() << "No goniometric system with index " << index << endl; + return Rad; +} diff --git a/kig/misc/goniometry.h b/kig/misc/goniometry.h new file mode 100644 index 00000000..eae84ced --- /dev/null +++ b/kig/misc/goniometry.h @@ -0,0 +1,72 @@ +// This file is part of Kig, a KDE program for Interactive Geometry... +// Copyright (C) 2004 Dominique Devriese <devriese@kde.org> +// Copyright (C) 2004 Pino Toscano <toscano.pino@tiscali.it> + +// 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. + +#ifndef KIG_MISC_GONIOMETRY_H +#define KIG_MISC_GONIOMETRY_H + +#include <qstringlist.h> + +/** + * Manage an angle and convert it from/to other goniometric systems. + */ +class Goniometry +{ +public: + enum System { Deg, Rad, Grad }; + Goniometry(); + Goniometry( double value, Goniometry::System system ); + ~Goniometry(); + void setValue( double value ); + const double value() const; + /** + * Set the system of the current angle to \p system, but it doesn't + * convert the value to the new system. + * + * \see convertTo() + */ + void setSystem( Goniometry::System system ); + /** + * Set the system of the current angle to \p system and convert the + * value to the new system using \ref convert(). + * + * \see setSystem() + */ + void convertTo( Goniometry::System system ); + const Goniometry::System system() const; + double getValue( Goniometry::System system ); + /** + * The most useful method of this class: convert the specified + * \p angle from the system \p from to the system \p to. + */ + static double convert( const double angle, const Goniometry::System from, const Goniometry::System to ); + /** + * Get a list of the supported goniometric systems. + */ + static QStringList systemList(); + static Goniometry::System intToSystem( const int index ); + + Goniometry& operator= ( const Goniometry& g ); + +private: + double mvalue; + typedef Goniometry::System goniosys; + goniosys msys; +}; + +#endif diff --git a/kig/misc/guiaction.cc b/kig/misc/guiaction.cc new file mode 100644 index 00000000..d4be4ded --- /dev/null +++ b/kig/misc/guiaction.cc @@ -0,0 +1,367 @@ +// 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 "guiaction.h" +#include "guiaction.moc" + +#include "coordinate_system.h" +#include "coordinate.h" +#include "object_constructor.h" + +#include "../kig/kig_part.h" +#include "../kig/kig_document.h" +#include "../misc/kiginputdialog.h" +#include "../modes/construct_mode.h" +#include "../modes/label.h" +#include "../objects/object_holder.h" +#include "../objects/object_factory.h" +#include "../objects/bogus_imp.h" + +#include <kiconloader.h> +#include <klocale.h> + +#include <qregexp.h> + +int GUIAction::shortcut() const +{ + return 0; +} + +GUIAction::~GUIAction() +{ +} + +ConstructibleAction::~ConstructibleAction() +{ +} + +ConstructibleAction::ConstructibleAction( + ObjectConstructor* ctor, + const QCString& actionname, + int shortcut ) + : GUIAction(), mctor( ctor ), mactionname( actionname ), mshortcut( shortcut ) +{ +} + +QString ConstructibleAction::description() const +{ + return mctor->description(); +} + +QCString ConstructibleAction::iconFileName() const +{ + return mctor->iconFileName(); +} + +QString ConstructibleAction::descriptiveName() const +{ + return mctor->descriptiveName(); +} + +void ConstructibleAction::act( KigPart& d ) +{ + BaseConstructMode* m = mctor->constructMode( d ); + d.runMode( m ); + delete m; +} + +KigGUIAction::KigGUIAction( GUIAction* act, + KigPart& doc, + QObject* parent ) + : KAction( act->descriptiveName(), + doc.instance()->iconLoader()->loadIcon( + act->iconFileName(), KIcon::Toolbar, 0, KIcon::DefaultState, 0L, true ), + act->shortcut(), + 0, 0, // no slot connection + parent, act->actionName() ), + mact( act ), + mdoc( doc ) +{ + setWhatsThis( act->description() ); + QString tooltip = act->descriptiveName(); + tooltip.replace( QRegExp( "&&" ), "&" ); + setToolTip( tooltip ); +} + +void KigGUIAction::slotActivated() +{ + mact->act( mdoc ); +} + +const char* ConstructibleAction::actionName() const +{ + return mactionname; +} + +ConstructPointAction::~ConstructPointAction() +{ +} + +QString ConstructPointAction::description() const +{ + return i18n( + "A normal point, i.e. one that is either independent or attached " + "to a line, circle, segment." + ); +} + +QCString ConstructPointAction::iconFileName() const +{ + return "point"; +} + +QString ConstructPointAction::descriptiveName() const +{ + return i18n("Point"); +} + +const char* ConstructPointAction::actionName() const +{ + return mactionname; +} + +int ConstructPointAction::shortcut() const +{ + return Qt::Key_P; +} + +void ConstructPointAction::act( KigPart& d ) +{ + PointConstructMode m( d ); + d.runMode( &m ); +} + +ConstructPointAction::ConstructPointAction( const char* actionname ) + : mactionname( actionname ) +{ +} + +GUIAction* KigGUIAction::guiAction() +{ + return mact; +} + +void KigGUIAction::plug( KigPart* doc ) +{ + mact->plug( doc, this ); +} + +void ConstructibleAction::plug( KigPart* doc, KigGUIAction* kact ) +{ + mctor->plug( doc, kact ); +} + +QString ConstructTextLabelAction::description() const +{ + return i18n( "Construct a text label." ); +} + +QCString ConstructTextLabelAction::iconFileName() const +{ + return "kig_text"; +} + +QString ConstructTextLabelAction::descriptiveName() const +{ + return i18n( "Text Label" ); +} + +const char* ConstructTextLabelAction::actionName() const +{ + return mactionname; +} + +void ConstructTextLabelAction::act( KigPart& d ) +{ + TextLabelConstructionMode m( d ); + d.runMode( &m ); +} + +ConstructTextLabelAction::ConstructTextLabelAction( const char* actionname ) + : mactionname( actionname ) +{ +} + +QString AddFixedPointAction::description() const +{ + return i18n( "Construct a Point by its Coordinates" ); +} + +QCString AddFixedPointAction::iconFileName() const +{ + return "pointxy"; +} + +QString AddFixedPointAction::descriptiveName() const +{ + return i18n( "Point by Coordinates" ); +} + +const char* AddFixedPointAction::actionName() const +{ + return mactionname; +} + +void AddFixedPointAction::act( KigPart& doc ) +{ + bool ok; + Coordinate c = Coordinate::invalidCoord(); + KigInputDialog::getCoordinate( + i18n( "Fixed Point" ), + i18n( "Enter the coordinates for the new point." ) + + QString::fromLatin1( "<br>" ) + + doc.document().coordinateSystem().coordinateFormatNoticeMarkup(), + doc.widget(), &ok, doc.document(), &c ); + if ( ! ok ) return; + ObjectHolder* p = ObjectFactory::instance()->fixedPoint( c ); + p->calc( doc.document() ); + doc.addObject( p ); +} + +AddFixedPointAction::AddFixedPointAction( const char* actionname ) + : mactionname( actionname ) +{ +} + +AddFixedPointAction::~AddFixedPointAction() +{ +} + +void GUIAction::plug( KigPart*, KigGUIAction* ) +{ +} + +int ConstructibleAction::shortcut() const +{ + return mshortcut; +} + +int ConstructTextLabelAction::shortcut() const +{ + return Qt::Key_B; +} + +int AddFixedPointAction::shortcut() const +{ + return Qt::Key_F; +} + +#if 0 +TestAction::TestAction( const char* actionname ) + : mactionname( actionname ) +{ +} + +TestAction::~TestAction() +{ +} + +QString TestAction::description() const +{ + return QString::fromLatin1( "Test stuff !!!" ); +} + +QCString TestAction::iconFileName() const +{ + return "new"; +} + +QString TestAction::descriptiveName() const +{ + return QString::fromLatin1( "Test stuff !!!" ); +} + +const char* TestAction::actionName() const +{ + return mactionname; +} + +void TestAction::act( KigPart& doc ) +{ + const char* script = + "def calc( a ):\n\treturn Point( a.coordinate() + Coordinate( 2, 0 ) )\n"; + Object* constantpoint = ObjectFactory::instance()->fixedPoint( Coordinate( -1, -1 ) ); + constantpoint->calc( doc ); + + Object* codeobject = new DataObject( new StringImp( QString::fromLatin1( script ) ) ); + Object* compiledcode = new RealObject( PythonCompileType::instance(), Objects( codeobject ) ); + compiledcode->calc( doc ); + + Objects args( compiledcode ); + args.push_back( constantpoint ); + Object* scriptobject = new RealObject( PythonExecuteType::instance(), args ); + scriptobject->calc( doc ); + + doc.addObject( constantpoint ); + doc.addObject( scriptobject ); +} + +#endif // if 0 ( TestAction ) + +#ifdef KIG_ENABLE_PYTHON_SCRIPTING +#include "../scripting/python_type.h" +#include "../scripting/script_mode.h" + +NewScriptAction::NewScriptAction( const char* descname, const char* description, + const char* actionname, const ScriptType::Type type, + const char* icon ) + : GUIAction(), mactionname( actionname ), mdescname( descname ), + mdescription( description ), micon( icon ), mtype( type ) +{ + if ( QString( micon ).isEmpty() ) + { + micon = ScriptType::icon( type ); + } +} + +NewScriptAction::~NewScriptAction() +{ +} + +QString NewScriptAction::description() const +{ + return i18n( mdescription ); +} + +QCString NewScriptAction::iconFileName() const +{ + return micon; +} + +QString NewScriptAction::descriptiveName() const +{ + return i18n( mdescname ); +} + +const char* NewScriptAction::actionName() const +{ + return mactionname; +} + +void NewScriptAction::act( KigPart& doc ) +{ + ScriptCreationMode m( doc ); + m.setScriptType( mtype ); + doc.runMode( &m ); +} + +int NewScriptAction::shortcut() const +{ + return 0; +} + +#endif // if KIG_ENABLE_PYTHON_SCRIPTING ( NewScriptAction ) diff --git a/kig/misc/guiaction.h b/kig/misc/guiaction.h new file mode 100644 index 00000000..c188a492 --- /dev/null +++ b/kig/misc/guiaction.h @@ -0,0 +1,174 @@ +// 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. + +#ifndef KIG_MISC_GUIACTION_H +#define KIG_MISC_GUIACTION_H + +#include <config.h> + +#ifdef KIG_ENABLE_PYTHON_SCRIPTING +#include "../scripting/script-common.h" +#endif + +#include <qstring.h> +#include <qcstring.h> +#include <kaction.h> + +class GUIAction; +class KigPart; + +class KigGUIAction + : public KAction +{ + Q_OBJECT + GUIAction* mact; + KigPart& mdoc; +public: + KigGUIAction( GUIAction* act, + KigPart& doc, + QObject* parent ); + void slotActivated(); + + GUIAction* guiAction(); + + void plug( KigPart* doc ); +}; + +class GUIAction +{ +public: + virtual ~GUIAction(); + + virtual QString description() const = 0; + virtual QCString iconFileName() const = 0; + virtual QString descriptiveName() const = 0; + virtual const char* actionName() const = 0; + virtual int shortcut() const = 0; + virtual void act( KigPart& ) = 0; + + virtual void plug( KigPart* doc, KigGUIAction* kact ); +}; + +class ObjectConstructor; + +class ConstructibleAction + : public GUIAction +{ + ObjectConstructor* mctor; + QCString mactionname; + int mshortcut; +public: + ConstructibleAction( ObjectConstructor* ctor, const QCString& actionname, + int shortcut = 0 ); + ~ConstructibleAction(); + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + int shortcut() const; + void act( KigPart& ); + void plug( KigPart* doc, KigGUIAction* kact ); +}; + +class ConstructPointAction + : public GUIAction +{ + const char* mactionname; +public: + ConstructPointAction( const char* actionname ); + ~ConstructPointAction(); + + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + int shortcut() const; + void act( KigPart& ); +}; + +class ConstructTextLabelAction + : public GUIAction +{ + const char* mactionname; +public: + ConstructTextLabelAction( const char* actionname ); + + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + int shortcut() const; + void act( KigPart& ); +}; + +class AddFixedPointAction + : public GUIAction +{ + const char* mactionname; +public: + AddFixedPointAction( const char* actionname ); + ~AddFixedPointAction(); + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + int shortcut() const; + void act( KigPart& ); +}; + +#if 0 +class TestAction + : public GUIAction +{ + const char* mactionname; +public: + TestAction( const char* actionname ); + ~TestAction(); + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + void act( KigPart& ); +}; +#endif + +#ifdef KIG_ENABLE_PYTHON_SCRIPTING + +class NewScriptAction + : public GUIAction +{ + const char* mactionname; + const char* mdescname; + const char* mdescription; + const char* micon; + const ScriptType::Type mtype; +public: + NewScriptAction( const char* descname, const char* description, + const char* actionname, const ScriptType::Type type, + const char* icon = "" ); + ~NewScriptAction(); + QString description() const; + QCString iconFileName() const; + QString descriptiveName() const; + const char* actionName() const; + void act( KigPart& ); + int shortcut() const; +}; + +#endif // KIG_ENABLE_PYTHON_SCRIPTING + +#endif diff --git a/kig/misc/kigfiledialog.cc b/kig/misc/kigfiledialog.cc new file mode 100644 index 00000000..6b8d8cb4 --- /dev/null +++ b/kig/misc/kigfiledialog.cc @@ -0,0 +1,81 @@ +// Copyright (C) 2005 Pino Toscano <toscano.pino@tiscali.it> + +// 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 "kigfiledialog.h" +#include "kigfiledialog.moc" + +#include <qfile.h> +#include <qpoint.h> + +#include <klocale.h> +#include <kmessagebox.h> + +KigFileDialog::KigFileDialog( const QString& startDir, const QString& filter, + const QString& caption, QWidget* parent ) + : KFileDialog( startDir, filter, parent, "kigfiledialog", true ), + mow( 0L ) +{ + setCaption( caption ); + setOperationMode( Saving ); + setMode( KFile::File | KFile::LocalOnly ); + moptcaption = i18n( "Options" ); +} + +void KigFileDialog::setOptionsWidget( QWidget* w ) +{ + mow = w; +} + +void KigFileDialog::accept() +{ + // i know this is an ugly hack, but i hadn't found other ways to get + // the selected file name _before_ the dialog is accept()'ed or + // reject()'ed... in every case, below we make sure to accept() or + // reject()... + setResult( QDialog::Accepted ); + + QString sFile = selectedFile(); + if ( QFile::exists( sFile ) ) + { + int ret = KMessageBox::warningContinueCancel( this, + i18n( "The file \"%1\" already exists. Do you wish to overwrite it?" ) + .arg( sFile ), i18n( "Overwrite File?" ), i18n("Overwrite") ); + if ( ret != KMessageBox::Continue ) + { + KFileDialog::reject(); + return; + } + } + if ( mow ) + { + KDialogBase* optdlg = new KDialogBase( + this, "optdlg", true, moptcaption, Cancel|Ok, Cancel, true ); + mow->reparent( optdlg, QPoint() ); + optdlg->setMainWidget( mow ); + optdlg->exec() == QDialog::Accepted ? KFileDialog::accept() : KFileDialog::reject(); + } + else + KFileDialog::accept(); +} + +void KigFileDialog::setOptionCaption( const QString& caption ) +{ + if ( caption.isEmpty() ) + return; + + moptcaption = caption; +} diff --git a/kig/misc/kigfiledialog.h b/kig/misc/kigfiledialog.h new file mode 100644 index 00000000..0337236d --- /dev/null +++ b/kig/misc/kigfiledialog.h @@ -0,0 +1,77 @@ +// Copyright (C) 2005 Pino Toscano <toscano.pino@tiscali.it> + +// 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 + +#ifndef KIG_MISC_KIGFILEDIALOG_H +#define KIG_MISC_KIGFILEDIALOG_H + +#include <kfiledialog.h> + +/** + * This file dialog is pretty like KFileDialog, but allow us to make an option + * widget popup to the user. + */ +class KigFileDialog + : public KFileDialog +{ + Q_OBJECT + +private: + /** + * Options widget + */ + QWidget* mow; + + QString moptcaption; + +public: + /** + * Construct a new KigFileDialog. + * + * \param startDir the start dir of the file dialog. Consult the + * documentation of KFileDialog for more help about this + * \param filter the filter for the file dialog + * \param caption the caption of this file dialog + * \param parent the parent for this file dialog + */ + KigFileDialog( const QString& startDir, const QString& filter, + const QString& caption, QWidget *parent ); + + /** + * Use this to set the widget containing the options of eg an export filter. + * The option widget will be popped up in a dialog right after the user + * presses OK and before the dialog is closed. + * + * You can construct the option widget with no parent, as it will be + * reparented. + * + * \param w the option widget + */ + void setOptionsWidget( QWidget* w ); + + /** + * Set the caption of the option dialog + * + * \param caption the caption of the option dialog + */ + void setOptionCaption( const QString& caption ); + +protected slots: + virtual void accept(); + +}; + +#endif diff --git a/kig/misc/kiginputdialog.cc b/kig/misc/kiginputdialog.cc new file mode 100644 index 00000000..8a2c38e5 --- /dev/null +++ b/kig/misc/kiginputdialog.cc @@ -0,0 +1,283 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2005 Pino Toscano <toscano.pino@tiscali.it> + + 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 "kiginputdialog.h" +#include "kiginputdialog.moc" + +#include "coordinate.h" +#include "coordinate_system.h" +#include "goniometry.h" + +#include "../kig/kig_document.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qvalidator.h> +#include <qwhatsthis.h> + +#include <kcombobox.h> +#include <kdebug.h> +#include <klineedit.h> +#include <klocale.h> +#include <ktextedit.h> + +class KigInputDialogPrivate +{ +public: + KigInputDialogPrivate(); + + QLabel* m_label; + KLineEdit* m_lineEditFirst; + KLineEdit* m_lineEditSecond; + KComboBox* m_comboBox; + KTextEdit* m_textEdit; + + Coordinate m_coord1; + Coordinate m_coord2; + KigDocument m_doc; + QValidator* m_vtor; + Goniometry m_gonio; + bool m_gonioIsNum; +}; + +KigInputDialogPrivate::KigInputDialogPrivate() + : m_label( 0L ), m_lineEditFirst( 0L ), m_lineEditSecond( 0L ), m_comboBox( 0L ), + m_textEdit( 0L ) +{ +} + +KigInputDialog::KigInputDialog( const QString& caption, const QString& label, + QWidget* parent, const KigDocument& doc, Coordinate* c1, Coordinate* c2 ) + : KDialogBase( parent, "kigdialog", true, caption, Ok|Cancel, Cancel, true ), + d( new KigInputDialogPrivate() ) +{ + d->m_coord1 = c1 ? Coordinate( *c1 ) : Coordinate::invalidCoord(); + d->m_coord2 = c2 ? Coordinate( *c2 ) : Coordinate::invalidCoord(); + d->m_doc = doc; + d->m_vtor = d->m_doc.coordinateSystem().coordinateValidator(); + + int deltay = 0; + bool ok = false; + + QFrame* frame = makeMainWidget(); + QVBoxLayout* mainlay = new QVBoxLayout( frame, 0, spacingHint() ); + mainlay->activate(); + + d->m_textEdit = new KTextEdit( frame ); + d->m_textEdit->setText( label ); + d->m_textEdit->setReadOnly( true ); + d->m_textEdit->setFocusPolicy( NoFocus ); +// d->m_textEdit->setAlignment( d->m_textEdit->alignment() | Qt::WordBreak ); + d->m_textEdit->setFrameStyle( QFrame::NoFrame ); + mainlay->addWidget( d->m_textEdit ); + + d->m_lineEditFirst = new KLineEdit( frame ); +// d->m_lineEditFirst->setValidator( d->m_vtor ); + if ( d->m_coord1.valid() ) + { + d->m_lineEditFirst->setText( d->m_doc.coordinateSystem().fromScreen( d->m_coord1, d->m_doc ) ); + ok = true; + } + mainlay->addWidget( d->m_lineEditFirst ); + + connect( d->m_lineEditFirst, SIGNAL(textChanged(const QString&)), + this, SLOT(slotCoordsChanged(const QString&)) ); + + if ( d->m_coord2.valid() ) + { + d->m_lineEditSecond = new KLineEdit( frame ); +// d->m_lineEditSecond->setValidator( d->m_vtor ); + d->m_lineEditSecond->setText( d->m_doc.coordinateSystem().fromScreen( d->m_coord2, d->m_doc ) ); + mainlay->addWidget( d->m_lineEditSecond ); + + connect( d->m_lineEditSecond, SIGNAL(textChanged(const QString&)), + this, SLOT(slotCoordsChanged(const QString&)) ); + + deltay += d->m_lineEditSecond->height() + spacingHint(); + } + + resize( 400, 160 + deltay ); + + d->m_lineEditFirst->setFocus(); + + enableButtonOK( ok ); +} + +KigInputDialog::KigInputDialog( QWidget* parent, const Goniometry& g ) + : KDialogBase( parent, "kigdialog", true, i18n( "Set Angle Size" ), Ok|Cancel, Cancel, true ), + d( new KigInputDialogPrivate() ) +{ + d->m_gonio = g; + d->m_gonioIsNum = true; + + QFrame* frame = makeMainWidget(); + QVBoxLayout* mainlay = new QVBoxLayout( frame, 0, spacingHint() ); + mainlay->activate(); + + d->m_label = new QLabel( frame ); + d->m_label->setText( i18n( "Insert the new size of this angle:" ) ); + mainlay->addWidget( d->m_label ); + + QHBoxLayout* horlay = new QHBoxLayout( 0, 0, spacingHint() ); + horlay->activate(); + + d->m_lineEditFirst = new KLineEdit( frame ); + d->m_lineEditFirst->setText( QString::number( d->m_gonio.value() ) ); + QWhatsThis::add( + d->m_lineEditFirst, + i18n( "Use this edit field to modify the size of this angle." ) ); + horlay->addWidget( d->m_lineEditFirst ); + + d->m_comboBox = new KComboBox( frame ); + d->m_comboBox->insertStringList( Goniometry::systemList() ); + d->m_comboBox->setCurrentItem( d->m_gonio.system() ); + QWhatsThis::add( + d->m_comboBox, + i18n( "Choose from this list the goniometric unit you want to use to " + "modify the size of this angle.<br>\n" + "If you switch to another unit, the value in the edit field on " + "the left will be converted to the new selected unit." ) ); + horlay->addWidget( d->m_comboBox ); + + mainlay->addLayout( horlay ); + + connect( d->m_lineEditFirst, SIGNAL(textChanged(const QString&)), + this, SLOT(slotGonioTextChanged(const QString&)) ); + connect( d->m_comboBox, SIGNAL(activated(int)), + this, SLOT(slotGonioSystemChanged(int)) ); + + resize( 350, 100 ); + + d->m_lineEditFirst->setFocus(); +} + +void KigInputDialog::keyPressEvent( QKeyEvent* e ) +{ + if ( ( e->key() == Qt::Key_Return ) && ( e->state() == 0 ) ) + { + if ( actionButton( Ok )->isEnabled() ) + { + actionButton( Ok )->animateClick(); + e->accept(); + return; + } + } + else if ( ( e->key() == Qt::Key_Escape ) && ( e->state() == 0 ) ) + { + actionButton( Cancel )->animateClick(); + e->accept(); + return; + } + +} + +void KigInputDialog::slotCoordsChanged( const QString& ) +{ + int p = 0; + QString t = d->m_lineEditFirst->text(); + bool ok = d->m_vtor->validate( t, p ) == QValidator::Acceptable; + if ( ok ) + d->m_coord1 = d->m_doc.coordinateSystem().toScreen( t, ok ); + if ( d->m_lineEditSecond ) + { + p = 0; + t = d->m_lineEditSecond->text(); + ok &= d->m_vtor->validate( t, p ) == QValidator::Acceptable; + if ( ok ) + d->m_coord2 = d->m_doc.coordinateSystem().toScreen( t, ok ); + } + + enableButtonOK( ok ); +} + +void KigInputDialog::slotGonioSystemChanged( int index ) +{ + if ( d->m_gonioIsNum ) + { + Goniometry::System newsys = Goniometry::intToSystem( index ); + d->m_gonio.convertTo( newsys ); + d->m_lineEditFirst->setText( QString::number( d->m_gonio.value() ) ); + } +} + +void KigInputDialog::slotGonioTextChanged( const QString& txt ) +{ + if ( txt.isNull() ) + d->m_gonioIsNum = false; + else + { + double v = txt.toDouble( &(d->m_gonioIsNum) ); + d->m_gonio.setValue( v ); + } + enableButtonOK( d->m_gonioIsNum ); +} + + +Coordinate KigInputDialog::coordinateFirst() const +{ + return d->m_coord1; +} + +Coordinate KigInputDialog::coordinateSecond() const +{ + return d->m_coord2; +} + +Goniometry KigInputDialog::goniometry() const +{ + return d->m_gonio; +} + +void KigInputDialog::getCoordinate( const QString& caption, const QString& label, + QWidget* parent, bool* ok, const KigDocument& doc, Coordinate* cvalue ) +{ + getTwoCoordinates( caption, label, parent, ok, doc, cvalue, 0 ); +} + +void KigInputDialog::getTwoCoordinates( const QString& caption, const QString& label, + QWidget* parent, bool* ok, const KigDocument& doc, Coordinate* cvalue, + Coordinate* cvalue2 ) +{ + KigInputDialog dlg( caption, label, parent, doc, cvalue, cvalue2 ); + + *ok = ( dlg.exec() == Accepted ); + + if ( *ok ) + { + Coordinate a = dlg.coordinateFirst(); + *cvalue = a; + if ( cvalue2 ) + { + Coordinate b = dlg.coordinateSecond(); + *cvalue2 = b; + } + } + +} + +Goniometry KigInputDialog::getAngle( QWidget* parent, bool* ok, const Goniometry& g ) +{ + KigInputDialog dlg( parent, g ); + + *ok = ( dlg.exec() == Accepted ); + + return dlg.goniometry(); +} diff --git a/kig/misc/kiginputdialog.h b/kig/misc/kiginputdialog.h new file mode 100644 index 00000000..afdd303d --- /dev/null +++ b/kig/misc/kiginputdialog.h @@ -0,0 +1,123 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2005 Pino Toscano <toscano.pino@tiscali.it> + + 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 +**/ + +#ifndef KIG_MISC_KIGINPUTDIALOG_H +#define KIG_MISC_KIGINPUTDIALOG_H + +class QString; +class Coordinate; +class Goniometry; +class KigDocument; +class KigInputDialogPrivate; + +#include <kdialogbase.h> + +/** + * The KigInputDialog class provides easy ways of interaction with the user. + * For example, it provides a flexible way to get one or two coordinates at + * once. + * + * It provides several static convenience functions: getCoordinate(), + * getTwoCoordinates(), getAngle(). + */ +class KigInputDialog + : KDialogBase +{ +Q_OBJECT + +public: + +private: + KigInputDialog( const QString& caption, const QString& label, QWidget* parent, + const KigDocument& doc, Coordinate* c1, Coordinate* c2 ); + KigInputDialog( QWidget* parent, const Goniometry& g ); + + virtual void keyPressEvent( QKeyEvent* e ); + + KigInputDialogPrivate* const d; + friend class KInputDialogPrivate; + + Coordinate coordinateFirst() const; + Coordinate coordinateSecond() const; + Goniometry goniometry() const; + +private slots: + void slotCoordsChanged( const QString& ); + void slotGonioSystemChanged( int index ); + void slotGonioTextChanged( const QString& txt ); + +public: + /** + * Static convenience function to get a Coordinate from the user. + * + * \param caption caption of the dialog + * \param label text of the label of the dialog + * \param parent parent of the dialog widget + * \param ok it will be set to true if the user pressed Ok after inserting a + * well-formatted Coordinate + * \param doc the actual Kig document + * \param cvalue a pointer to a Coordinate class. If the user inserted + * successfully a new Coordinate, the value will be stored + * here. If this points to a valid Coordinate, then it will be + * displayed as initial value of the correspondenting text edit + */ + static void getCoordinate( const QString& caption, const QString& label, + QWidget* parent, bool* ok, const KigDocument& doc, Coordinate* cvalue ); + + /** + * Static convenience function to get two Coordinates at once from the user. + * + * \param caption caption of the dialog + * \param label text of the label of the dialog + * \param parent parent of the dialog widget + * \param ok it will be set to true if the user pressed Ok after inserting + * well-formatted Coordinates + * \param doc the actual Kig document + * \param cvalue a pointer to a Coordinate class. If the user inserted + * successfully new Coordinates, the value of the first + * Coordinate will be stored here. If this points to a valid + * Coordinate, then it will be displayed as initial value of + * the text edit representing the first Coordinate. + * \param cvalue2 a pointer to a Coordinate class. If the user inserted + * successfully new Coordinates, the value of the second + * Coordinate will be stored here. If this points to a valid + * Coordinate, then it will be displayed as initial value of + * the text edit representing the second Coordinate. + */ + static void getTwoCoordinates( const QString& caption, const QString& label, + QWidget* parent, bool* ok, const KigDocument& doc, Coordinate* cvalue, + Coordinate* cvalue2 ); + + /** + * Static convenience function to get an angle incapsulated in a Goniometry + * class. + * + * \param parent parent of the dialog widget + * \param ok it will be set to true if the user pressed Ok after inserting a + * well-formatted angle + * \param g the Goniometry class containing the original angle we are going + * to modify. + * + * \return a Goniometry class containing the new angle + */ + static Goniometry getAngle( QWidget* parent, bool* ok, const Goniometry& g ); +}; + +#endif diff --git a/kig/misc/kignumerics.cpp b/kig/misc/kignumerics.cpp new file mode 100644 index 00000000..4711d058 --- /dev/null +++ b/kig/misc/kignumerics.cpp @@ -0,0 +1,389 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + + 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 "kignumerics.h" +#include "common.h" + +/* + * compute one of the roots of a cubic polynomial + * if xmin << 0 or xmax >> 0 then autocompute a bound for all the + * roots + */ + +double calcCubicRoot ( double xmin, double xmax, double a, + double b, double c, double d, int root, bool& valid, int& numroots ) +{ + // renormalize: positive a and infinity norm = 1 + + double infnorm = fabs(a); + if ( infnorm < fabs(b) ) infnorm = fabs(b); + if ( infnorm < fabs(c) ) infnorm = fabs(c); + if ( infnorm < fabs(d) ) infnorm = fabs(d); + if ( a < 0 ) infnorm = -infnorm; + a /= infnorm; + b /= infnorm; + c /= infnorm; + d /= infnorm; + + const double small = 1e-7; + valid = false; + if ( fabs(a) < small ) + { + if ( fabs(b) < small ) + { + if ( fabs(c) < small ) + { // degree = 0; + numroots = 0; + return 0.0; + } + // degree = 1 + double rootval = -d/c; + numroots = 1; + if ( rootval < xmin || xmax < rootval ) numroots--; + if ( root > numroots ) return 0.0; + valid = true; + return rootval; + } + // degree = 2 + if ( b < 0 ) { b = -b; c = -c; d = -d; } + double discrim = c*c - 4*b*d; + numroots = 2; + if ( discrim < 0 ) + { + numroots = 0; + return 0.0; + } + discrim = sqrt(discrim)/(2*fabs(b)); + double rootmiddle = -c/(2*b); + if ( rootmiddle - discrim < xmin ) numroots--; + if ( rootmiddle + discrim > xmax ) numroots--; + if ( rootmiddle + discrim < xmin ) numroots--; + if ( rootmiddle - discrim > xmax ) numroots--; + if ( root > numroots ) return 0.0; + valid = true; + if ( root == 2 || rootmiddle - discrim < xmin ) return rootmiddle + discrim; + return rootmiddle - discrim; + } + + if ( xmin < -1e8 || xmax > 1e8 ) + { + + // compute a bound for all the real roots: + + xmax = fabs(d/a); + if ( fabs(c/a) + 1 > xmax ) xmax = fabs(c/a) + 1; + if ( fabs(b/a) + 1 > xmax ) xmax = fabs(b/a) + 1; + xmin = -xmax; + } + + // computing the coefficients of the Sturm sequence + double p1a = 2*b*b - 6*a*c; + double p1b = b*c - 9*a*d; + double p0a = c*p1a*p1a + p1b*(3*a*p1b - 2*b*p1a); + + int varbottom = calcCubicVariations (xmin, a, b, c, d, p1a, p1b, p0a); + int vartop = calcCubicVariations (xmax, a, b, c, d, p1a, p1b, p0a); + numroots = vartop - varbottom; + valid = false; + if (root <= varbottom || root > vartop ) return 0.0; + + valid = true; + + // now use bisection to separate the required root + double dx = (xmax - xmin)/2; + while ( vartop - varbottom > 1 ) + { + if ( fabs( dx ) < 1e-8 ) return (xmin + xmax)/2; + double xmiddle = xmin + dx; + int varmiddle = calcCubicVariations (xmiddle, a, b, c, d, p1a, p1b, p0a); + if ( varmiddle < root ) // I am below + { + xmin = xmiddle; + varbottom = varmiddle; + } else { + xmax = xmiddle; + vartop = varmiddle; + } + dx /= 2; + } + + /* + * now [xmin, xmax] enclose a single root, try using Newton + */ + if ( vartop - varbottom == 1 ) + { + double fval1 = a; // double check... + double fval2 = a; + fval1 = b + xmin*fval1; + fval2 = b + xmax*fval2; + fval1 = c + xmin*fval1; + fval2 = c + xmax*fval2; + fval1 = d + xmin*fval1; + fval2 = d + xmax*fval2; + assert ( fval1 * fval2 <= 0 ); + return calcCubicRootwithNewton ( xmin, xmax, a, b, c, d, 1e-8 ); + } + else // probably a double root here! + return ( xmin + xmax )/2; +} + +/* + * computation of the number of sign changes in the sturm sequence for + * a third degree polynomial at x. This number counts the number of + * roots of the polynomial on the left of point x. + * + * a, b, c, d: coefficients of the third degree polynomial (a*x^3 + ...) + * + * the second degree polynomial in the sturm sequence is just minus the + * derivative, so we don't need to compute it. + * + * p1a*x + p1b: is the third (first degree) polynomial in the sturm sequence. + * + * p0a: is the (constant) fourth polynomial of the sturm sequence. + */ + +int calcCubicVariations (double x, double a, double b, double c, + double d, double p1a, double p1b, double p0a) +{ + double fval, fpval; + fval = fpval = a; + fval = b + x*fval; + fpval = fval + x*fpval; + fval = c + x*fval; + fpval = fval + x*fpval; + fval = d + x*fval; + + double f1val = p1a*x + p1b; + + bool f3pos = fval >= 0; + bool f2pos = fpval <= 0; + bool f1pos = f1val >= 0; + bool f0pos = p0a >= 0; + + int variations = 0; + if ( f3pos != f2pos ) variations++; + if ( f2pos != f1pos ) variations++; + if ( f1pos != f0pos ) variations++; + return variations; +} + +/* + * use newton to solve a third degree equation with already isolated + * root + */ + +inline void calcCubicDerivatives ( double x, double a, double b, double c, + double d, double& fval, double& fpval, double& fppval ) +{ + fval = fpval = fppval = a; + fval = b + x*fval; + fpval = fval + x*fpval; + fppval = fpval + x*fppval; // this is really half the second derivative + fval = c + x*fval; + fpval = fval + x*fpval; + fval = d + x*fval; +} + +double calcCubicRootwithNewton ( double xmin, double xmax, double a, + double b, double c, double d, double tol ) +{ + double fval, fpval, fppval; + + double fval1, fval2, fpval1, fpval2, fppval1, fppval2; + calcCubicDerivatives ( xmin, a, b, c, d, fval1, fpval1, fppval1 ); + calcCubicDerivatives ( xmax, a, b, c, d, fval2, fpval2, fppval2 ); + assert ( fval1 * fval2 <= 0 ); + + assert ( xmax > xmin ); + while ( xmax - xmin > tol ) + { + // compute the values of function, derivative and second derivative: + assert ( fval1 * fval2 <= 0 ); + if ( fppval1 * fppval2 < 0 || fpval1 * fpval2 < 0 ) + { + double xmiddle = (xmin + xmax)/2; + calcCubicDerivatives ( xmiddle, a, b, c, d, fval, fpval, fppval ); + if ( fval1*fval <= 0 ) + { + xmax = xmiddle; + fval2 = fval; + fpval2 = fpval; + fppval2 = fppval; + } else { + xmin = xmiddle; + fval1 = fval; + fpval1 = fpval; + fppval1 = fppval; + } + } else + { + // now we have first and second derivative of constant sign, we + // can start with Newton from the Fourier point. + double x = xmin; + if ( fval2*fppval2 > 0 ) x = xmax; + double p = 1.0; + int iterations = 0; + while ( fabs(p) > tol && iterations++ < 100 ) + { + calcCubicDerivatives ( x, a, b, c, d, fval, fpval, fppval ); + p = fval/fpval; + x -= p; + } + if( iterations >= 100 ) + { + // Newton scheme did not converge.. + // we should end up with an invalid Coordinate + return double_inf; + }; + return x; + } + } + + // we cannot apply Newton, (perhaps we are at an inflection point) + + return (xmin + xmax)/2; +} + +/* + * This function computes the LU factorization of a mxn matrix, with + * m typically less then n. This is done with complete pivoting; the + * exchanges in columns are recorded in the integer vector "exchange" + */ +bool GaussianElimination( double *matrix[], int numrows, + int numcols, int exchange[] ) +{ + // start gaussian elimination + for ( int k = 0; k < numrows; ++k ) + { + // ricerca elemento di modulo massimo + double maxval = -double_inf; + int imax = k; + int jmax = k; + for( int i = k; i < numrows; ++i ) + { + for( int j = k; j < numcols; ++j ) + { + if (fabs(matrix[i][j]) > maxval) + { + maxval = fabs(matrix[i][j]); + imax = i; + jmax = j; + } + } + } + + // row exchange + if ( imax != k ) + for( int j = k; j < numcols; ++j ) + { + double t = matrix[k][j]; + matrix[k][j] = matrix[imax][j]; + matrix[imax][j] = t; + } + + // column exchange + if ( jmax != k ) + for( int i = 0; i < numrows; ++i ) + { + double t = matrix[i][k]; + matrix[i][k] = matrix[i][jmax]; + matrix[i][jmax] = t; + } + + // remember this column exchange at step k + exchange[k] = jmax; + + // we can't usefully eliminate a singular matrix.. + if ( maxval == 0. ) return false; + + // ciclo sulle righe + for( int i = k+1; i < numrows; ++i) + { + double mik = matrix[i][k]/matrix[k][k]; + matrix[i][k] = mik; //ricorda il moltiplicatore... (not necessary) + // ciclo sulle colonne + for( int j = k+1; j < numcols; ++j ) + { + matrix[i][j] -= mik*matrix[k][j]; + } + } + } + return true; +} + +/* + * solve an undetermined homogeneous triangular system. the matrix is nonzero + * on its diagonal. The last unknown(s) are chosen to be 1. The + * vector "exchange" contains exchanges to be performed on the + * final solution components. + */ + +void BackwardSubstitution( double *matrix[], int numrows, int numcols, + int exchange[], double solution[] ) +{ + // the system is homogeneous and underdetermined, the last unknown(s) + // are chosen = 1 + for ( int j = numrows; j < numcols; ++j ) + { + solution[j] = 1.0; // other choices are possible here + }; + + for( int k = numrows - 1; k >= 0; --k ) + { + // backward substitution + solution[k] = 0.0; + for ( int j = k+1; j < numcols; ++j) + { + solution[k] -= matrix[k][j]*solution[j]; + } + solution[k] /= matrix[k][k]; + } + + // ultima fase: riordinamento incognite + + for( int k = numrows - 1; k >= 0; --k ) + { + int jmax = exchange[k]; + double t = solution[k]; + solution[k] = solution[jmax]; + solution[jmax] = t; + } +} + +bool Invert3by3matrix ( const double m[3][3], double inv[3][3] ) +{ + double det = m[0][0]*(m[1][1]*m[2][2] - m[1][2]*m[2][1]) - + m[0][1]*(m[1][0]*m[2][2] - m[1][2]*m[2][0]) + + m[0][2]*(m[1][0]*m[2][1] - m[1][1]*m[2][0]); + if (det == 0) return false; + + for (int i=0; i < 3; i++) + { + for (int j=0; j < 3; j++) + { + int i1 = (i+1)%3; + int i2 = (i+2)%3; + int j1 = (j+1)%3; + int j2 = (j+2)%3; + inv[j][i] = (m[i1][j1]*m[i2][j2] - m[i1][j2]*m[i2][j1])/det; + } + } + return true; +} diff --git a/kig/misc/kignumerics.h b/kig/misc/kignumerics.h new file mode 100644 index 00000000..7beef59f --- /dev/null +++ b/kig/misc/kignumerics.h @@ -0,0 +1,47 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + + 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 +**/ + +#ifndef KIG_MISC_KIGNUMERICS_H +#define KIG_MISC_KIGNUMERICS_H + +#include <cmath> + +double calcCubicRoot ( double xmin, double xmax, double a, + double b, double c, double d, int root, bool& valid, int& numroots ); + +int calcCubicVariations (double x, double a, double b, double c, + double d, double p1a, double p1b, double p0a); + +double calcCubicRootwithNewton ( double ymin, double ymax, double a, + double b, double c, double d, double tol ); + +/** + * Gaussian Elimination. We return false if the matrix is singular, + * and can't be usefully eliminated.. + */ +bool GaussianElimination( double *matrix[], int numrows, int numcols, + int scambio[] ); + +void BackwardSubstitution( double *matrix[], int numrows, int numcols, + int scambio[], double solution[] ); + +bool Invert3by3matrix ( const double m[3][3], double inv[3][3] ); + +#endif // KIG_MISC_KIGNUMERICS_H diff --git a/kig/misc/kigpainter.cpp b/kig/misc/kigpainter.cpp new file mode 100644 index 00000000..e2b2f440 --- /dev/null +++ b/kig/misc/kigpainter.cpp @@ -0,0 +1,953 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002-2003 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 "kigpainter.h" + +#include "../kig/kig_view.h" +#include "../kig/kig_document.h" +#include "../misc/goniometry.h" +#include "../objects/object_holder.h" +#include "../objects/curve_imp.h" +#include "../objects/point_imp.h" +#include "object_hierarchy.h" +#include "common.h" +#include "conic-common.h" +#include "cubic-common.h" +#include "coordinate_system.h" + +#include <qpen.h> + +#include <cmath> +#include <stack> +#include <functional> +#include <algorithm> + +KigPainter::KigPainter( const ScreenInfo& si, QPaintDevice* device, + const KigDocument& doc, bool no ) + : mP ( device ), + color( Qt::blue ), + style( Qt::SolidLine ), + pointstyle( 0 ), + width( -1 ), + brushStyle( Qt::NoBrush ), + brushColor( Qt::blue ), + mdoc( doc ), + msi( si ), + mNeedOverlay( no ), + overlayenlarge( 0 ) +{ + mP.setBackgroundColor( Qt::white ); +} + +KigPainter::~KigPainter() +{ +} + +void KigPainter::drawRect( const Rect& r ) +{ + Rect rt = r.normalized(); + QRect qr = toScreen(rt); + qr.normalize(); + mP.drawRect(qr); + if( mNeedOverlay ) mOverlay.push_back( qr ); +} + +void KigPainter::drawRect( const QRect& r ) +{ + mP.drawRect(r); + if( mNeedOverlay ) mOverlay.push_back( r ); +} + +void KigPainter::drawCircle( const Coordinate& center, const double radius ) +{ + Coordinate bottomLeft = center - Coordinate(radius, radius); + Coordinate topRight = center + Coordinate(radius, radius); + Rect r( bottomLeft, topRight ); + QRect qr = toScreen( r ); + mP.drawEllipse ( qr ); + if( mNeedOverlay ) circleOverlay( center, radius ); +} + +void KigPainter::drawSegment( const Coordinate& from, const Coordinate& to ) +{ + QPoint tF = toScreen(from), tT = toScreen(to); + mP.drawLine( tF, tT ); + if( mNeedOverlay ) segmentOverlay( from, to ); +} + +void KigPainter::drawFatPoint( const Coordinate& p ) +{ + int twidth = width == -1 ? 5 : width; + mP.setPen( QPen( color, 1, style ) ); + switch ( pointstyle ) + { + case 0: + { + double radius = twidth * pixelWidth(); + setBrushStyle( Qt::SolidPattern ); + Coordinate rad( radius, radius ); + rad /= 2; + Coordinate tl = p - rad; + Coordinate br = p + rad; + Rect r( tl, br ); + QRect qr = toScreen( r ); + mP.drawEllipse( qr ); + if( mNeedOverlay ) mOverlay.push_back( qr ); + break; + } + case 1: + { + double radius = twidth * pixelWidth(); + setBrushStyle( Qt::NoBrush ); + Coordinate rad( radius, radius ); + rad /= 2; + Coordinate tl = p - rad; + Coordinate br = p + rad; + Rect r( tl, br ); + QRect qr = toScreen( r ); + mP.drawEllipse( qr ); + if( mNeedOverlay ) mOverlay.push_back( qr ); + break; + } + case 2: + { + double radius = twidth * pixelWidth(); + Coordinate rad( radius, radius ); + rad /= 2; + Coordinate tl = p - rad; + Coordinate br = p + rad; + Rect r( tl, br ); + QRect qr = toScreen( r ); + mP.drawRect( qr ); + mP.fillRect( qr, QBrush( color, Qt::SolidPattern ) ); + if( mNeedOverlay ) mOverlay.push_back( qr ); + break; + } + case 3: + { + double radius = twidth * pixelWidth(); + Coordinate rad( radius, radius ); + rad /= 2; + Coordinate tl = p - rad; + Coordinate br = p + rad; + Rect r( tl, br ); + QRect qr = toScreen( r ); + mP.drawRect( qr ); + if( mNeedOverlay ) mOverlay.push_back( qr ); + break; + } + case 4: + { + double radius = twidth * pixelWidth(); + Coordinate rad( radius, radius ); + rad /= 2; + Coordinate tl = p - rad; + Coordinate br = p + rad; + Rect r( tl, br ); + QRect qr = toScreen( r ); + mP.setPen( QPen( color, 2 ) ); + mP.drawLine( qr.topLeft(), qr.bottomRight() ); + mP.drawLine( qr.topRight(), qr.bottomLeft() ); + if( mNeedOverlay ) mOverlay.push_back( qr ); + break; + } + } + mP.setPen( QPen( color, twidth, style ) ); +} + +void KigPainter::drawPoint( const Coordinate& p ) +{ + mP.drawPoint( toScreen(p) ); + if( mNeedOverlay ) pointOverlay( p ); +} + +void KigPainter::drawLine( const Coordinate& p1, const Coordinate& p2 ) +{ + drawLine( LineData( p1, p2 ) ); +} + +void KigPainter::drawText( const Rect p, const QString s, int textFlags, int len ) +{ + QRect t = toScreen(p); + int tf = textFlags; + t.moveBy( 2, 2 ); + t.setWidth( t.width() - 4 ); + t.setHeight( t.height() - 4 ); + mP.drawText( t, tf, s, len ); + if( mNeedOverlay ) textOverlay( t, s, tf, len ); +} + +void KigPainter::textOverlay( const QRect& r, const QString s, int textFlags, int len ) +{ + // kdDebug() << Rect::fromQRect( mP.boundingRect( r, textFlags, s, len ) ) << endl; + QRect newr( mP.boundingRect( r, textFlags, s, len ) ); + newr.setWidth( newr.width() + 4 ); + newr.setHeight( newr.height() + 4 ); + mOverlay.push_back( newr ); +} + +const Rect KigPainter::boundingRect( const Rect& r, const QString s, + int f, int l ) const +{ + QRect qr = mP.boundingRect( toScreen( r ), f, s, l ); + qr.setWidth( qr.width() + 4 ); + qr.setHeight( qr.height() + 4 ); + return fromScreen( qr ); +} + +void KigPainter::setColor( const QColor& c ) +{ + color = c; + mP.setPen( QPen( color, width == -1 ? 1 : width, style ) ); +} + +void KigPainter::setStyle( const PenStyle c ) +{ + style = c; + mP.setPen( QPen( color, width == -1 ? 1 : width, style ) ); +} + +void KigPainter::setWidth( const int c ) +{ + width = c; + if (c > 0) overlayenlarge = c - 1; + mP.setPen( QPen( color, width == -1 ? 1 : width, style ) ); +} + +void KigPainter::setPointStyle( const int p ) +{ + pointstyle = p; +} + +void KigPainter::setPen( const QPen& p ) +{ + color = p.color(); + width = p.width(); + style = p.style(); + mP.setPen(p); +} + +void KigPainter::setBrush( const QBrush& b ) +{ + brushStyle = b.style(); + brushColor = b.color(); + mP.setBrush( b ); +} + +void KigPainter::setBrushStyle( const BrushStyle c ) +{ + brushStyle = c; + mP.setBrush( QBrush( brushColor, brushStyle ) ); +} + +void KigPainter::setBrushColor( const QColor& c ) +{ + brushColor = c; + mP.setBrush( QBrush( brushColor, brushStyle ) ); +} + +bool KigPainter::getNightVision( ) const +{ + return mdoc.getNightVision(); +} + +QColor KigPainter::getColor() const +{ + return color; +} + +/* +static void setContains( QRect& r, const QPoint& p ) +{ + if ( r.left() > p.x() ) r.setLeft( p.x() ); + if ( r.right() < p.x() ) r.setRight( p.x() ); + // this is correct, i think. In qt the bottom has the highest y + // coord... + if ( r.bottom() > p.y() ) r.setBottom( p.y() ); + if ( r.top() < p.y() ) r.setTop( p.y() ); +} +*/ + +void KigPainter::drawPolygon( const std::vector<QPoint>& pts, + bool winding, int index, int npoints ) +{ + QPen oldpen = mP.pen(); + QBrush oldbrush = mP.brush(); + setBrush( QBrush( color, Dense4Pattern ) ); + setPen( Qt::NoPen ); + // i know this isn't really fast, but i blame it all on Qt with its + // stupid container classes... what's wrong with the STL ? + QPointArray t( pts.size() ); + int c = 0; + for( std::vector<QPoint>::const_iterator i = pts.begin(); i != pts.end(); ++i ) + { + t.putPoints( c++, 1, i->x(), i->y() ); + }; + mP.drawPolygon( t, winding, index, npoints ); + setPen( oldpen ); + setBrush( oldbrush ); + if( mNeedOverlay ) mOverlay.push_back( t.boundingRect() ); +} + +void KigPainter::drawArea( const std::vector<Coordinate>& pts, bool border ) +{ + QPen oldpen = mP.pen(); + QBrush oldbrush = mP.brush(); + setBrush( QBrush( color, SolidPattern ) ); + if ( border ) + setPen( QPen( color, width == -1 ? 1 : width ) ); + else + setPen( Qt::NoPen ); + QPointArray t( pts.size() ); + int c = 0; + for( std::vector<Coordinate>::const_iterator i = pts.begin(); i != pts.end(); ++i ) + { + QPoint p = toScreen( *i ); + t.putPoints( c++, 1, p.x(), p.y() ); + } + mP.drawPolygon( t ); + setPen( oldpen ); + setBrush( oldbrush ); + if( mNeedOverlay ) mOverlay.push_back( t.boundingRect() ); +} + +Rect KigPainter::window() +{ + return msi.shownRect(); +} + +void KigPainter::circleOverlayRecurse( const Coordinate& centre, + double radiussq, + const Rect& cr ) +{ + Rect currentRect = cr.normalized(); + + if( !currentRect.intersects( window() ) ) return; + + // this code is an adaptation of Marc Bartsch's code, from KGeo + Coordinate tl = currentRect.topLeft(); + Coordinate br = currentRect.bottomRight(); + Coordinate tr = currentRect.topRight(); + Coordinate bl = currentRect.bottomLeft(); + Coordinate c = currentRect.center(); + + // mp: we compute the minimum and maximum distance from the center + // of the circle and this rect + double distxmin = 0, distxmax = 0, distymin = 0, distymax = 0; + if ( centre.x >= tr.x ) distxmin = centre.x - tr.x; + if ( centre.x <= bl.x ) distxmin = bl.x - centre.x; + if ( centre.y >= tr.y ) distymin = centre.y - tr.y; + if ( centre.y <= bl.y ) distymin = bl.y - centre.y; + distxmax = fabs(centre.x - c.x) + currentRect.width()/2; + distymax = fabs(centre.y - c.y) + currentRect.height()/2; + // this should take into account the thickness of the line... + distxmin -= pixelWidth(); + if (distxmin < 0) distxmin = 0; + distxmax += pixelWidth(); + distymin -= pixelWidth(); + if (distymin < 0) distymin = 0; + distymax += pixelWidth(); + double distminsq = distxmin*distxmin + distymin*distymin; + double distmaxsq = distxmax*distxmax + distymax*distymax; + + // if the circle doesn't touch this rect, we return + // too far from the centre + if (distminsq > radiussq) return; + + // too near to the centre + if (distmaxsq < radiussq) return; + + // the rect contains some of the circle + // -> if it's small enough, we keep it + if( currentRect.width() < overlayRectSize() ) { + mOverlay.push_back( toScreenEnlarge( currentRect) ); + } else { + // this func works recursive: we subdivide the current rect, and if + // it is of a good size, we keep it, otherwise we handle it again + double width = currentRect.width() / 2; + double height = currentRect.height() / 2; + Rect r1 ( c, -width, -height); + r1.normalize(); + circleOverlayRecurse(centre, radiussq, r1); + Rect r2 ( c, width, -height); + r2.normalize(); + circleOverlayRecurse(centre, radiussq, r2); + Rect r3 ( c, -width, height); + r3.normalize(); + circleOverlayRecurse(centre, radiussq, r3); + Rect r4 ( c, width, height); + r4.normalize(); + circleOverlayRecurse(centre, radiussq, r4); + }; +} + +void KigPainter::circleOverlay( const Coordinate& centre, double radius ) +{ + double t = radius + pixelWidth(); + Coordinate r( t, t ); + Coordinate bottomLeft = centre - r; + Coordinate topRight = centre + r; + Rect rect( bottomLeft, topRight ); + circleOverlayRecurse ( centre , radius*radius, rect ); +} + +void KigPainter::segmentOverlay( const Coordinate& p1, const Coordinate& p2 ) +{ + // this code is based upon what Marc Bartsch wrote for KGeo + + // some stuff we may need: + Coordinate p3 = p2 - p1; + Rect border = window(); +// double length = p3.length(); + // mp: using the l-infinity distance is more natural here + double length = fabs(p3.x); + if ( fabs( p3.y ) > length ) length = fabs( p3.y ); + if ( length < pixelWidth() ) + { + // hopefully prevent SIGZERO's + mOverlay.push_back( toScreen( Rect( p1, p2 ) ) ); + return; + }; + p3 *= overlayRectSize(); + p3 /= length; + + int counter = 0; + + Rect r(p1, p2); + r.normalize(); + + for (;;) { + Rect tR( Coordinate( 0, 0 ), overlayRectSize(), overlayRectSize() ); + Coordinate tP = p1+p3*counter; + tR.setCenter(tP); + if (!tR.intersects(r)) + { + //kdDebug()<< "stopped after "<< counter << " passes." << endl; + break; + } + if (tR.intersects(border)) mOverlay.push_back( toScreenEnlarge( tR ) ); + if (++counter > 100) + { + kdDebug()<< k_funcinfo << "counter got too big :( " << endl; + break; + } + } +} + +double KigPainter::overlayRectSize() +{ + return 20 * pixelWidth(); +} + +void KigPainter::pointOverlay( const Coordinate& p1 ) +{ + Rect r( p1, 3*pixelWidth(), 3*pixelWidth()); + r.setCenter( p1 ); + mOverlay.push_back( toScreen( r) ); +} + +double KigPainter::pixelWidth() +{ + return msi.pixelWidth(); +} + +void KigPainter::setWholeWinOverlay() +{ + mOverlay.clear(); + mOverlay.push_back( mP.viewport() ); + // don't accept any more overlay's... + mNeedOverlay = false; +} + +QPoint KigPainter::toScreen( const Coordinate p ) const +{ + return msi.toScreen( p ); +} + +void KigPainter::drawGrid( const CoordinateSystem& c, bool showGrid, bool showAxes ) +{ + c.drawGrid( *this, showGrid, showAxes ); + setWholeWinOverlay(); +} + +void KigPainter::drawObject( const ObjectHolder* o, bool ss ) +{ + o->draw( *this, ss ); +} + +void KigPainter::drawObjects( const std::vector<ObjectHolder*>& os, bool sel ) +{ + drawObjects( os.begin(), os.end(), sel ); +} + +void KigPainter::drawFilledRect( const QRect& r ) +{ + QPen pen( Qt::black, 1, Qt::DotLine ); + setPen( pen ); + setBrush( QBrush( Qt::cyan, Dense6Pattern ) ); + drawRect( r.normalize() ); +} + +void KigPainter::drawTextStd( const QPoint& p, const QString& s ) +{ + if ( s.isNull() ) return; + // tf = text formatting flags + int tf = AlignLeft | AlignTop | DontClip | WordBreak; + // we need the rect where we're going to paint text + setPen(QPen(Qt::blue, 1, SolidLine)); + setBrush(Qt::NoBrush); + drawText( Rect( + msi.fromScreen(p), window().bottomRight() + ).normalized(), s, tf ); + +} + +QRect KigPainter::toScreen( const Rect r ) const +{ + return msi.toScreen( r ); +} + +QRect KigPainter::toScreenEnlarge( const Rect r ) const +{ + if ( overlayenlarge == 0 ) return msi.toScreen( r ); + + QRect qr = msi.toScreen( r ); + qr.moveBy ( -overlayenlarge, -overlayenlarge ); + int w = qr.width(); + int h = qr.height(); + qr.setWidth (w + 2*overlayenlarge); + qr.setHeight (h + 2*overlayenlarge); + return qr; +} + +void KigPainter::drawSimpleText( const Coordinate& c, const QString s ) +{ + int tf = AlignLeft | AlignTop | DontClip | WordBreak; + drawText( c, s, tf); +} + +void KigPainter::drawText( const Coordinate p, const QString s, + int textFlags, int len ) +{ + drawText( Rect( p, mP.window().right(), mP.window().top() ), + s, textFlags, len ); +} +const Rect KigPainter::simpleBoundingRect( const Coordinate& c, const QString s ) +{ + int tf = AlignLeft | AlignTop | DontClip | WordBreak; + return boundingRect( c, s, tf ); +} + +const Rect KigPainter::boundingRect( const Coordinate& c, const QString s, + int f, int l ) const +{ + return boundingRect( Rect( c, mP.window().right(), mP.window().top() ), + s, f, l ); +} + +Coordinate KigPainter::fromScreen( const QPoint& p ) const +{ + return msi.fromScreen( p ); +} + +Rect KigPainter::fromScreen( const QRect& r ) const +{ + return msi.fromScreen( r ); +} + +void KigPainter::drawRay( const Coordinate& a, const Coordinate& b ) +{ + Coordinate tb = b; + calcRayBorderPoints( a, tb, window() ); + drawSegment( a, tb ); +} + +typedef std::pair<double,Coordinate> coordparampair; + +struct workitem +{ + workitem( coordparampair f, coordparampair s, Rect *o) : + first(f), second(s), overlay(o) {} + coordparampair first; + coordparampair second; + Rect *overlay; +}; + +void KigPainter::drawLine( const LineData& d ) +{ + if ( d.a != d.b ) + { + LineData l = calcBorderPoints( d, window() ); + drawSegment( l.a, l.b ); + } +} + +void KigPainter::drawSegment( const LineData& d ) +{ + drawSegment( d.a, d.b ); +} + +void KigPainter::drawRay( const LineData& d ) +{ + drawRay( d.a, d.b ); +} + +void KigPainter::drawAngle( const Coordinate& cpoint, const double dstartangle, + const double dangle ) +{ + // convert to 16th of degrees... + const int startangle = static_cast<int>( Goniometry::convert( 16 * dstartangle, Goniometry::Rad, Goniometry::Deg ) ); + const int angle = static_cast<int>( Goniometry::convert( 16 * dangle, Goniometry::Rad, Goniometry::Deg ) ); + + QPoint point = toScreen( cpoint ); + +// int radius = mP.window().width() / 5; + int radius = 50; + QRect surroundingRect( 0, 0, radius*2, radius*2 ); + surroundingRect.moveCenter( point ); + + mP.drawArc( surroundingRect, startangle, angle ); + + // now for the arrow... + QPoint end( static_cast<int>( point.x() + radius * cos( dstartangle + dangle ) ), + static_cast<int>( point.y() - radius * sin( dstartangle + dangle ) ) ); + QPoint vect = (end - point); + double vectlen = sqrt( float( vect.x() * vect.x() + vect.y() * vect.y() ) ); + QPoint orthvect( -vect.y(), vect.x() ); + vect = vect * 6 / vectlen; + orthvect = orthvect * 6 / vectlen; + + QPointArray arrow( 3 ); + arrow.setPoint( 0, end ); + arrow.setPoint( 1, end + orthvect + vect ); + arrow.setPoint( 2, end + orthvect - vect ); +// std::vector<QPoint> arrow; +// arrow.push_back( end ); +// arrow.push_back( end + orthvect + vect ); +// arrow.push_back( end + orthvect - vect ); + + setBrushStyle( Qt::SolidPattern ); +// drawPolygon( arrow ); + mP.drawPolygon( arrow, false, 0, -1 ); + +// if ( mNeedOverlay ) mOverlay.push_back( toScreen( r ) ); + setWholeWinOverlay(); //mp: ugly! why not compute a correct overlay? + // mOverlay.push_back( arrow.boundingRect() ); +} + +void KigPainter::drawPolygon( const std::vector<Coordinate>& pts, + bool winding, int index, int npoints ) +{ + using namespace std; + vector<QPoint> points; + for ( uint i = 0; i < pts.size(); ++i ) + points.push_back( toScreen( pts[i] ) ); + drawPolygon( points, winding, index, npoints ); +} + +void KigPainter::drawVector( const Coordinate& a, const Coordinate& b ) +{ + // bugfix... + if ( a == b ) return; + // the segment + drawSegment( a, b ); + // the arrows... + Coordinate dir = b - a; + Coordinate perp( dir.y, -dir.x ); + double length = perp.length(); + perp *= 10* pixelWidth(); + perp /= length; + dir *= 10 * pixelWidth(); + dir /= length; + Coordinate c = b - dir + perp; + Coordinate d = b - dir - perp; + // draw the arrow lines with a normal style + mP.setPen( QPen( color, width == -1 ? 1 : width, Qt::SolidLine ) ); + drawSegment( b, c ); + drawSegment( b, d ); + // setting again the original style + mP.setPen( QPen( color, width == -1 ? 1 : width, style ) ); +} + +/* *** this function is commented out *** +inline Coordinate locusGetCoord( double p, const CurveImp* curve, const ObjectHierarchy& h, + bool& valid, const KigDocument& doc ) +{ + Coordinate pt = curve->getPoint( p, valid, doc ); + if ( ! valid ) return Coordinate(); + PointImp pimp( pt ); + Args args; + args.push_back( &pimp ); + std::vector<ObjectImp*> calced = h.calc( args, doc ); + assert( calced.size() == 1 ); + ObjectImp* o = calced.front(); + Coordinate ret; + if ( o->inherits( ObjectImp::ID_PointImp ) ) + { + valid = true; + ret = static_cast<PointImp*>( o )->coordinate(); + } + else + valid = false; + delete o; + return ret; +}; +*/ + +class CurveImpPointCalcer +{ + const CurveImp* curve; +public: + CurveImpPointCalcer( const CurveImp* c ) + : curve( c ) + { + } + static const double endinterval; + inline const Coordinate getPoint( double param, const KigDocument& d ) const { + return curve->getPoint( param, d ); + } +}; + +const double CurveImpPointCalcer::endinterval = 1.; + +void KigPainter::drawCurve( const CurveImp* curve ) +{ + // we manage our own overlay + bool tNeedOverlay = mNeedOverlay; + mNeedOverlay = false; + + QPen pen = mP.pen(); + + // this stack contains pairs of Coordinates ( parameter intervals ) + // that we still need to process: + std::stack<workitem> workstack; + // mp: this stack contains all the generated overlays: + // the strategy for generating the overlay structure is the same + // recursive-like used to draw the segments: a new rectangle is + // generated whenever the length of a segment becomes lower than + // overlayRectSize(), or if the segment would be drawn anyway + // to avoid strange things from happening we impose that the distance + // in parameter space be less than a threshold before generating + // any overlay. + // + // The third parameter in workitem is a pointer into a stack of + // all generated rectangles (in real coordinate space); if 0 + // there is no rectangles associated to that segment yet. + // + // Using the final mOverlay stack would be much more efficient, but + // 1. needs transformations into window space + // 2. would be more difficult to drop rectangles not intersecting + // the window. + std::stack<Rect> overlaystack; + + // mp: the original version in which an initial set of 20 intervals + // were pushed onto the stack is replaced by a single interval and + // by forcing subdivision till h < hmax (with more or less the same + // final result). + // First push the [0,1] interval into the stack: + + Coordinate coo1 = curve->getPoint( 0., mdoc ); + Coordinate coo2 = curve->getPoint( 1., mdoc ); + workstack.push( workitem( + coordparampair( 0., coo1 ), + coordparampair( 1., coo2 ), + 0 ) ); + + // maxlength is the square of the maximum size that we allow + // between two points.. + double maxlength = 1.5 * pixelWidth(); + maxlength *= maxlength; + // error squared is required to be less that sigma (half pixel) + double sigma = maxlength/4; + // distance between two parameter values cannot be too small + double hmin = 3e-5; + // distance between two parameter values cannot be too large + double hmax = 1./40; + double hmaxoverlay = 1./8; + + int count = 1; // the number of segments we've already + // visited... + static const int maxnumberofpoints = 1000; + + const Rect& sr = window(); + + // what this algorithm does is approximating the curve with a set of + // segments. we don't draw the individual segments, but use + // QPainter::drawPolyline() so that the line styles work properly. + // Possibly there are performance advantages as well ? this array + // is a buffer of the polyline approximation of the part of the + // curve that we are currently processing. + QPointArray curpolyline( 1000 ); + int curpolylinenextfree = 0; + + // we don't use recursion, but a stack based approach for efficiency + // concerns... + while ( ! workstack.empty() && count < maxnumberofpoints ) + { + workitem curitem = workstack.top(); + workstack.pop(); + bool curitemok = true; + while ( curitemok && count++ < maxnumberofpoints ) + { + double t0 = curitem.first.first; + double t1 = curitem.second.first; + Coordinate p0 = curitem.first.second; + bool valid0 = p0.valid(); + Coordinate p1 = curitem.second.second; + bool valid1 = p1.valid(); + + // we take the middle parameter of the two previous points... + double t2 = ( t0 + t1 ) / 2; + double h = fabs( t1 - t0 ) /2; + + // if exactly one of the two endpoints is invalid, then + // we prefer to find an internal value of the parameter + // separating valid points from invalid points. We use + // a bisection strategy (this is not implemented yet!) +// if ( ( valid0 && ! valid1 ) || ( valid1 && ! valid0 ) ) +// { +// while ( h >= hmin ) +// { +// ....................................... +// } +// } + + Rect *overlaypt = curitem.overlay; + Coordinate p2 = curve->getPoint( t2, mdoc ); + bool allvalid = p2.valid() && valid0 && valid1; + bool dooverlay = ! overlaypt && h < hmaxoverlay && valid0 && valid1 + && fabs( p0.x - p1.x ) <= overlayRectSize() + && fabs( p0.y - p1.y ) <= overlayRectSize(); + bool addn = sr.contains( p2 ) || h >= hmax; + // estimated error between the curve and the segments + double errsq = 1e21; + if ( allvalid ) errsq = (0.5*p0 + 0.5*p1 - p2).squareLength(); + errsq /= 4; + curitemok = false; +// bool dodraw = allvalid && h < hmax && ( errsq < sigma || h < hmin ); + bool dodraw = allvalid && h < hmax && errsq < sigma; + if ( tNeedOverlay && ( dooverlay || dodraw ) ) + { + Rect newoverlay( p0, p1 ); + overlaystack.push( newoverlay ); + overlaypt = &overlaystack.top(); + } + if ( overlaypt ) overlaypt->setContains( p2 ); + if ( dodraw ) + { + // draw the two segments + QPoint tp0 = toScreen(p0); + QPoint tp1 = toScreen(p1); + QPoint tp2 = toScreen(p2); + if ( curpolylinenextfree > 0 && curpolyline[curpolylinenextfree - 1] != tp1 ) + { + // flush the current part of the curve + mP.drawPolyline( curpolyline, 0, curpolylinenextfree ); + curpolylinenextfree = 0; + } + if ( curpolylinenextfree == 0 ) + curpolyline[curpolylinenextfree++] = tp1; + curpolyline[curpolylinenextfree++] = tp2; + curpolyline[curpolylinenextfree++] = tp0; + } + else if ( h >= hmin ) // we do not continue to subdivide indefinitely! + { + // push into stack in order to process both subintervals + if ( addn || ( valid0 && sr.contains( p0 ) ) ) + workstack.push( workitem( curitem.first, coordparampair( t2, p2 ), + overlaypt ) ); + if ( addn || ( valid1 && sr.contains( p1 ) ) ) + { + curitem = workitem( coordparampair( t2, p2 ), curitem.second , + overlaypt ); + curitemok = true; + } + } + } + } + // flush the rest of the curve + mP.drawPolyline( curpolyline, 0, curpolylinenextfree ); + curpolylinenextfree = 0; + + if ( ! workstack.empty () ) + kdDebug() << "Stack not empty in KigPainter::drawCurve!\n" << endl; + assert ( tNeedOverlay || overlaystack.empty() ); + if ( tNeedOverlay ) + { + Rect border = window(); + while ( ! overlaystack.empty() ) + { + Rect overlay = overlaystack.top(); + overlaystack.pop(); + if (overlay.intersects( border )) + mOverlay.push_back( toScreenEnlarge( overlay ) ); + } + } + mNeedOverlay = tNeedOverlay; +} + +void KigPainter::drawTextFrame( const Rect& frame, + const QString& s, bool needframe ) +{ + QPen oldpen = mP.pen(); + QBrush oldbrush = mP.brush(); + if ( needframe ) + { + // inspired upon kgeo, thanks to Marc Bartsch, i've taken some of + // his code too.. + setPen( QPen( Qt::black, 1 ) ); + setBrush( QBrush( QColor( 255, 255, 222 ) ) ); + drawRect( frame ); + setPen( QPen( QColor( 197, 194, 197 ), 1, Qt::SolidLine ) ); + + QRect qr = toScreen( frame ); + + mP.drawLine( qr.topLeft(), qr.topRight() ); + mP.drawLine( qr.topLeft(), qr.bottomLeft() ); + }; + setPen( oldpen ); + setBrush( oldbrush ); + drawText( frame, s, Qt::AlignVCenter | Qt::AlignLeft ); +} + +void KigPainter::drawArc( const Coordinate& center, const double radius, + const double dstartangle, const double dangle ) +{ + // convert to 16th of degrees... + const int startangle = static_cast<int>( Goniometry::convert( 16 * dstartangle, Goniometry::Rad, Goniometry::Deg ) ); + const int angle = static_cast<int>( Goniometry::convert( 16 * dangle, Goniometry::Rad, Goniometry::Deg ) ); + + if ( angle <= 16 ) + { + Coordinate a = center + radius * Coordinate( cos( dstartangle ), sin( dstartangle )); + Coordinate b = center + radius * Coordinate( cos( dstartangle + dangle ), sin( dstartangle + dangle )); + drawSegment ( a , b ); + } + else + { + Rect krect( 0, 0, 2*radius, 2*radius ); + krect.setCenter( center ); + QRect rect = toScreen( krect ); + + mP.drawArc( rect, startangle, angle ); + setWholeWinOverlay(); + } +} + diff --git a/kig/misc/kigpainter.h b/kig/misc/kigpainter.h new file mode 100644 index 00000000..e7f884f4 --- /dev/null +++ b/kig/misc/kigpainter.h @@ -0,0 +1,291 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002-2003 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 +**/ + + +#ifndef KIGPAINTER_H +#define KIGPAINTER_H + +#include "coordinate.h" +#include "rect.h" +#include "screeninfo.h" + +#include <qpainter.h> +#include <qcolor.h> + +#include <vector> + +class KigWidget; +class QPaintDevice; +class CoordinateSystem; +class ObjectHierarchy; +class ConicPolarData; +class CubicCartesianData; +class LineData; +class CurveImp; +class KigDocument; +class ObjectHolder; + +/** + * KigPainter is an extended QPainter. + * + * Currently the only difference is that it translates coordinates + * from and to the internal coordinates/ the widget coordinates... + * + * It calls KigWidget::appendOverlay() for all of the places it draws in... + */ +class KigPainter + : public Qt +{ +protected: + // don't blaim me for this mutable hack. It's TT that hasn't got + // its consts correctly... + mutable QPainter mP; + + QColor color; + PenStyle style; + int pointstyle; + int width; + BrushStyle brushStyle; + QColor brushColor; + + const KigDocument& mdoc; + ScreenInfo msi; + + bool mNeedOverlay; + int overlayenlarge; +public: + /** + * construct a new KigPainter: + * the ScreenInfo is used to map the document coordinates to the + * widget coordinates. This is done transparently to the objects. + * needOverlay sets whether we try to remember the places we're + * drawing on using the various overlay methods. @see overlay() + */ + KigPainter( const ScreenInfo& r, QPaintDevice* device, const KigDocument& doc, + bool needOverlay = true ); + ~KigPainter(); + + /** + * what rect are we drawing on ? + */ + Rect window(); + + QPoint toScreen( const Coordinate p ) const; + QRect toScreen( const Rect r ) const; + QRect toScreenEnlarge( const Rect r ) const; + Coordinate fromScreen( const QPoint& p ) const; + Rect fromScreen( const QRect& r ) const; + + // colors and stuff... + void setStyle( const PenStyle c ); + void setColor( const QColor& c ); + /** + * setting this to -1 means to use the default width for the object + * being drawn.. a point -> 5, other objects -> 1 + */ + void setWidth( const int c ); + void setPointStyle( const int p ); + void setPen( const QPen& p ); + void setBrushStyle( const BrushStyle c ); + void setBrush( const QBrush& b ); + void setBrushColor( const QColor& c ); + + QColor getColor() const; + bool getNightVision( ) const; + + double pixelWidth(); + + /** + * this is called by some drawing functions that modify the 'entire' + * screen, i.e. they do so many changes that it's easier to just + * update the entire screen, or else i have been to lazy to + * implement an appropriate overlay function ;) + * it clears mOverlay, and sets it to the entire widget... + */ + void setWholeWinOverlay(); + + /** + * draw an object ( by calling its draw function.. ) + */ + void drawObject( const ObjectHolder* o, bool sel ); + void drawObjects( const std::vector<ObjectHolder*>& os, bool sel ); + template<typename iter> + void drawObjects( iter begin, iter end, bool sel ) + { + for ( ; begin != end; ++begin ) + drawObject( *begin, sel ); + } + + /** + * draw a generic curve... + */ + void drawCurve( const CurveImp* curve ); + + /** + * draws text in a standard manner, convenience function... + */ + void drawTextStd( const QPoint& p, const QString& s ); + + /** + * draws a rect filled up with a pattern of cyan lines... + */ + void drawFilledRect( const QRect& ); + + /** + * draw a rect.. + */ + void drawRect( const Rect& r ); + + /** + * overload, mainly for drawing the selection rectangle by + * KigWidget... + */ + void drawRect( const QRect& r ); + + /** + * draw a circle... + */ + void drawCircle( const Coordinate& center, const double radius ); + + /** + * draw a segment... + */ + void drawSegment ( const Coordinate& from, const Coordinate& to ); + void drawSegment( const LineData& d ); + + /** + * draw a ray... + */ + void drawRay( const Coordinate& a, const Coordinate& b ); + void drawRay( const LineData& d ); + + /** + * draw a line... + */ + void drawLine ( const Coordinate& p1, const Coordinate& p2 ); + void drawLine( const LineData& d ); + + /** + * draw a point... This means a single point, as in + * QPainter::drawPoint(), unlike drawFatPoint()... + */ + void drawPoint( const Coordinate& p ); + + /** + * draw a thick point.. This is what the user sees when he draws a + * point. In fact it isn't a point, but a filled circle of a + * certain radius... + */ + void drawFatPoint( const Coordinate& p ); + + /** + * draw a polygon defined by the points in pts... + */ + void drawPolygon( const std::vector<QPoint>& pts, bool winding = false, int index = 0, int npoints = -1 ); + void drawPolygon( const std::vector<Coordinate>& pts, bool winding = false, int index = 0, int npoints = -1 ); + + /** + * draw an area defined by the points in pts filled with the set + * color... + */ + void drawArea( const std::vector<Coordinate>& pts, bool border = true ); + + /** + * draw the angle with center point, with size angle, starting + * at the angle startAngle.. Angles should be in radians. + */ + void drawAngle( const Coordinate& point, const double startangle, + const double angle ); + + /** + * draw the arc ( a part of a circle ), of the circle with center + * center, radius radius, with size angle, starting at the angle + * startAngle.. Angles should be in radians.. + */ + void drawArc( const Coordinate& center, const double radius, + const double startangle, const double angle ); + + /** + * draw a vector ( with an arrow etc. ) + */ + + void drawVector( const Coordinate& a, const Coordinate& b ); + + /** + * draw text... + * \see QPainter::drawText() + */ + void drawText( const Rect r, const QString s, int textFlags = 0, + int len = -1); + void drawText( const Coordinate p, const QString s, + int textFlags = 0, int len = -1); + + void drawSimpleText( const Coordinate& c, const QString s ); + void drawTextFrame( const Rect& frame, const QString& s, bool needframe ); + + const Rect boundingRect( const Rect& r, const QString s, + int f = 0, int l = -1 ) const; + + const Rect boundingRect( const Coordinate& c, const QString s, + int f = 0, int l = -1 ) const; + + const Rect simpleBoundingRect( const Coordinate& c, const QString s ); + + void drawGrid( const CoordinateSystem& c, bool showGrid = true, bool showAxes = true ); + + const std::vector<QRect>& overlay() { return mOverlay; } + +protected: + /** + * adds a number of rects to mOverlay so that the rects entirely + * contain the circle... + * \see mOverlay + */ + void circleOverlay( const Coordinate& centre, double radius ); + // this works recursively... + void circleOverlayRecurse( const Coordinate& centre, double radius, const Rect& currentRect ); + + /** + * adds some rects to mOverlay, so that they cover the segment p1p2 + * completely... + * \see Object::getOverlay() + */ + void segmentOverlay( const Coordinate& p1, const Coordinate& p2 ); + + /** + * ... + */ + void pointOverlay( const Coordinate& p1 ); + + /** + * ... + * \see drawText(), QPainter::boundingRect() + */ + void textOverlay( const QRect& r, const QString s, int textFlags, int len ); + + /** + * the size we want the overlay rects to be... + */ + double overlayRectSize(); + + std::vector<QRect> mOverlay; +}; + +#endif diff --git a/kig/misc/kigtransform.cpp b/kig/misc/kigtransform.cpp new file mode 100644 index 00000000..a297ed6e --- /dev/null +++ b/kig/misc/kigtransform.cpp @@ -0,0 +1,810 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + Copyright (C) 2003 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 "kigtransform.h" + +#include "kignumerics.h" +#include "common.h" + +#include <cmath> + +#include <klocale.h> +#include <kdebug.h> + +// Transformation getProjectiveTransformation ( int argsnum, +// Object *transforms[], bool& valid ) +// { +// valid = true; + +// assert ( argsnum > 0 ); +// int argn = 0; +// Object* transform = transforms[argn++]; +// if (transform->toVector()) +// { +// // translation +// assert (argn == argsnum); +// Vector* v = transform->toVector(); +// Coordinate dir = v->getDir(); +// return Transformation::translation( dir ); +// } + +// if (transform->toPoint()) +// { +// // point reflection ( or is point symmetry the correct term ? ) +// assert (argn == argsnum); +// Point* p = transform->toPoint(); +// return Transformation::pointReflection( p->getCoord() ); +// } + +// if (transform->toLine()) +// { +// // line reflection ( or is it line symmetry ? ) +// Line* line = transform->toLine(); +// assert (argn == argsnum); +// return Transformation::lineReflection( line->lineData() ); +// } + +// if (transform->toRay()) +// { +// // domi: sorry, but what kind of transformation does this do ? +// // i'm guessing it's some sort of rotation, but i'm not +// // really sure.. +// Ray* line = transform->toRay(); +// Coordinate d = line->direction().normalize(); +// Coordinate t = line->p1(); +// double alpha = 0.1*M_PI/2; // a small angle for the DrawPrelim +// if (argn < argsnum) +// { +// Angle* angle = transforms[argn++]->toAngle(); +// alpha = angle->size(); +// } +// assert (argn == argsnum); +// return Transformation::projectiveRotation( alpha, d, t ); +// } + +// if (transform->toAngle()) +// { +// // rotation.. +// Coordinate center = Coordinate( 0., 0. ); +// if (argn < argsnum) +// { +// Object* arg = transforms[argn++]; +// assert (arg->toPoint()); +// center = arg->toPoint()->getCoord(); +// } +// Angle* angle = transform->toAngle(); +// double alpha = angle->size(); + +// assert (argn == argsnum); + +// return Transformation::rotation( alpha, center ); +// } + +// if (transform->toSegment()) // this is a scaling +// { +// Segment* segment = transform->toSegment(); +// Coordinate p = segment->p2() - segment->p1(); +// double s = p.length(); +// if (argn < argsnum) +// { +// Object* arg = transforms[argn++]; +// if (arg->toSegment()) // s is the length of the first segment +// // divided by the length of the second.. +// { +// Segment* segment = arg->toSegment(); +// Coordinate p = segment->p2() - segment->p1(); +// s /= p.length(); +// if (argn < argsnum) arg = transforms[argn++]; +// } +// if (arg->toPoint()) // scaling w.r. to a point +// { +// Point* p = arg->toPoint(); +// assert (argn == argsnum); +// return Transformation::scaling( s, p->getCoord() ); +// } +// if (arg->toLine()) // scaling w.r. to a line +// { +// Line* line = arg->toLine(); +// assert( argn == argsnum ); +// return Transformation::scaling( s, line->lineData() ); +// } +// } + +// return Transformation::scaling( s, Coordinate( 0., 0. ) ); +// } + +// valid = false; +// return Transformation::identity(); +// } + +// tWantArgsResult WantTransformation ( Objects::const_iterator& i, +// const Objects& os ) +// { +// Object* o = *i++; +// if (o->toVector()) return tComplete; +// if (o->toPoint()) return tComplete; +// if (o->toLine()) return tComplete; +// if (o->toAngle()) +// { +// if ( i == os.end() ) return tNotComplete; +// o = *i++; +// if (o->toPoint()) return tComplete; +// if (o->toLine()) return tComplete; +// return tNotGood; +// } +// if (o->toRay()) +// { +// if ( i == os.end() ) return tNotComplete; +// o = *i++; +// if (o->toAngle()) return tComplete; +// return tNotGood; +// } +// if (o->toSegment()) +// { +// if ( i == os.end() ) return tNotComplete; +// o = *i++; +// if ( o->toSegment() ) +// { +// if ( i == os.end() ) return tNotComplete; +// o = *i++; +// } +// if (o->toPoint()) return tComplete; +// if (o->toLine()) return tComplete; +// return tNotGood; +// } +// return tNotGood; +// } + +// QString getTransformMessage ( const Objects& os, const Object *o ) +// { +// int size = os.size(); +// switch (size) +// { +// case 1: +// if (o->toVector()) return i18n("translate by this vector"); +// if (o->toPoint()) return i18n("central symmetry by this point. You" +// " can obtain different transformations by clicking on lines (mirror)," +// " vectors (translation), angles (rotation), segments (scaling) and rays" +// " (projective transformation)"); +// if (o->toLine()) return i18n("reflect in this line"); +// if (o->toAngle()) return i18n("rotate by this angle"); +// if (o->toSegment()) return i18n("scale using the length of this vector"); +// if (o->toRay()) return i18n("a projective transformation in the direction" +// " indicated by this ray, it is a rotation in the projective plane" +// " about a point at infinity"); +// return i18n("Use this transformation"); + +// case 2: // we ask for the first parameter of the transformation +// case 3: +// if (os[1]->toAngle()) +// { +// if (o->toPoint()) return i18n("about this point"); +// assert (false); +// } +// if (os[1]->toSegment()) +// { +// if (o->toSegment()) +// return i18n("relative to the length of this other vector"); +// if (o->toPoint()) +// return i18n("about this point"); +// if (o->toLine()) +// return i18n("about this line"); +// } +// if (os[1]->toRay()) +// { +// if (o->toAngle()) return i18n("rotate by this angle in the projective" +// " plane"); +// } +// return i18n("Using this object"); + +// default: assert(false); +// } + +// return i18n("Use this transformation"); +// } + + +/* domi: not necessary anymore, homotheticness is kept as a bool in + * the Transformation class.. + * keeping it here, in case a need for it arises some time in the + * future... + * decide if the given transformation is homotetic + */ +// bool isHomoteticTransformation ( double transformation[3][3] ) +// { +// if (transformation[0][1] != 0 || transformation[0][2] != 0) return (false); +// // test the orthogonality of the matrix 2x2 of second and third rows +// // and columns +// if (fabs(fabs(transformation[1][1]) - +// fabs(transformation[2][2])) > 1e-8) return (false); +// if (fabs(fabs(transformation[1][2]) - +// fabs(transformation[2][1])) > 1e-8) return (false); + +// return transformation[1][2] * transformation[2][1] * +// transformation[1][1] * transformation[2][2] <= 0.; +// } + +const Transformation Transformation::identity() +{ + Transformation ret; + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + ret.mdata[i][j] = ( i == j ? 1 : 0 ); + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::scalingOverPoint( double factor, const Coordinate& center ) +{ + Transformation ret; + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + ret.mdata[i][j] = ( i == j ? factor : 0 ); + ret.mdata[0][0] = 1; + ret.mdata[1][0] = center.x - factor * center.x; + ret.mdata[2][0] = center.y - factor * center.y; + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::translation( const Coordinate& c ) +{ + Transformation ret = identity(); + ret.mdata[1][0] = c.x; + ret.mdata[2][0] = c.y; + + // this is already set in the identity() constructor, but just for + // clarity.. + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::pointReflection( const Coordinate& c ) +{ + Transformation ret = scalingOverPoint( -1, c ); + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} + +const Transformation operator*( const Transformation& a, const Transformation& b ) +{ + // just multiply the two matrices.. + Transformation ret; + + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + { + ret.mdata[i][j] = 0; + for ( int k = 0; k < 3; ++k ) + ret.mdata[i][j] += a.mdata[i][k] * b.mdata[k][j]; + }; + + // combination of two homotheties is a homothety.. + + ret.mIsHomothety = a.mIsHomothety && b.mIsHomothety; + + // combination of two affinities is affine.. + + ret.mIsAffine = a.mIsAffine && b.mIsAffine; + + return ret; +} + +const Transformation Transformation::lineReflection( const LineData& l ) +{ + Transformation ret = scalingOverLine( -1, l ); + // a reflection is a homothety... + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::scalingOverLine( double factor, const LineData& l ) +{ + Transformation ret = identity(); + + Coordinate a = l.a; + Coordinate d = l.dir(); + double dirnormsq = d.squareLength(); + ret.mdata[1][1] = (d.x*d.x + factor*d.y*d.y)/dirnormsq; + ret.mdata[2][2] = (d.y*d.y + factor*d.x*d.x)/dirnormsq; + ret.mdata[1][2] = ret.mdata[2][1] = (d.x*d.y - factor*d.x*d.y)/dirnormsq; + + ret.mdata[1][0] = a.x - ret.mdata[1][1]*a.x - ret.mdata[1][2]*a.y; + ret.mdata[2][0] = a.y - ret.mdata[2][1]*a.x - ret.mdata[2][2]*a.y; + + // domi: is 1e-8 a good value ? + ret.mIsHomothety = ( fabs( factor - 1 ) < 1e-8 || fabs ( factor + 1 ) < 1e-8 ); + ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::harmonicHomology( + const Coordinate& center, const LineData& axis ) +{ + // this is a well known projective transformation. We find it by first + // computing the homogeneous equation of the axis ax + by + cz = 0 + // then a straightforward computation shows that the 3x3 matrix describing + // the transformation is of the form: + // + // (r . C) Id - 2 (C tensor r) + // + // where r = [c, a, b], C = [1, Cx, Cy], Cx and Cy are the coordinates of + // the center, '.' denotes the scalar product, Id is the identity matrix, + // 'tensor' is the tensor product producing a 3x3 matrix. + // + // note: here we decide to use coordinate '0' in place of the third coordinate + // in homogeneous notation; e.g. C = [1, cx, cy] + + Coordinate pointa = axis.a; + Coordinate pointb = axis.b; + + double a = pointa.y - pointb.y; + double b = pointb.x - pointa.x; + double c = pointa.x*pointb.y - pointa.y*pointb.x; + + double cx = center.x; + double cy = center.y; + + double scalprod = a*cx + b*cy + c; + scalprod *= 0.5; + Transformation ret; + + ret.mdata[0][0] = c - scalprod; + ret.mdata[0][1] = a; + ret.mdata[0][2] = b; + + ret.mdata[1][0] = c*cx; + ret.mdata[1][1] = a*cx - scalprod; + ret.mdata[1][2] = b*cx; + + ret.mdata[2][0] = c*cy; + ret.mdata[2][1] = a*cy; + ret.mdata[2][2] = b*cy - scalprod; + + ret.mIsHomothety = ret.mIsAffine = false; + return ret; +} + +const Transformation Transformation::affinityGI3P( + const std::vector<Coordinate>& FromPoints, + const std::vector<Coordinate>& ToPoints, + bool& valid ) +{ + // construct the (generically) unique affinity that transforms 3 given + // point into 3 other given points; i.e. it depends on the coordinates of + // a total of 6 points. This actually amounts in solving a 6x6 linear + // system to find the entries of a 2x2 linear transformation matrix T + // and of a translation vector t. + // If Pi denotes one of the starting points and Qi the corresponding + // final position we actually have to solve: Qi = t + T Pi, for i=1,2,3 + // (each one is a vector equation, so that it really gives 2 equations). + // In our context T and t are used to build a 3x3 projective transformation + // as follows: + // + // [ 1 0 0 ] + // [ t1 T11 T12 ] + // [ t2 T21 T22 ] + // + // In order to take advantage of the two functions "GaussianElimination" + // and "BackwardSubstitution", which are specifically aimed at solving + // homogeneous underdetermined linear systems, we just add a further + // unknown m and solve for t + T Pi - m Qi = 0. Since our functions + // returns a nonzero solution we shall have a nonzero 'm' in the end and + // can build the 3x3 matrix as follows: + // + // [ m 0 0 ] + // [ t1 T11 T12 ] + // [ t2 T21 T22 ] + // + // we order the unknowns as follows: m, t1, t2, T11, T12, T21, T22 + + double row0[7], row1[7], row2[7], row3[7], row4[7], row5[7]; + + double *matrix[6] = {row0, row1, row2, row3, row4, row5}; + + double solution[7]; + int scambio[7]; + + assert (FromPoints.size() == 3); + assert (ToPoints.size() == 3); + + // fill in the matrix elements + for ( int i = 0; i < 6; i++ ) + { + for ( int j = 0; j < 7; j++ ) + { + matrix[i][j] = 0.0; + } + } + + for ( int i = 0; i < 3; i++ ) + { + Coordinate p = FromPoints[i]; + Coordinate q = ToPoints[i]; + matrix[i][0] = -q.x; + matrix[i][1] = 1.0; + matrix[i][3] = p.x; + matrix[i][4] = p.y; + matrix[i+3][0] = -q.y; + matrix[i+3][2] = 1.0; + matrix[i+3][5] = p.x; + matrix[i+3][6] = p.y; + } + + Transformation ret; + valid = true; + if ( ! GaussianElimination( matrix, 6, 7, scambio ) ) + { valid = false; return ret; } + + // fine della fase di eliminazione + BackwardSubstitution( matrix, 6, 7, scambio, solution ); + + // now we can build the 3x3 transformation matrix; remember that + // unknown 0 is the multiplicator 'm' + + ret.mdata[0][0] = solution[0]; + ret.mdata[0][1] = ret.mdata[0][2] = 0.0; + ret.mdata[1][0] = solution[1]; + ret.mdata[2][0] = solution[2]; + ret.mdata[1][1] = solution[3]; + ret.mdata[1][2] = solution[4]; + ret.mdata[2][1] = solution[5]; + ret.mdata[2][2] = solution[6]; + + ret.mIsHomothety = false; + ret.mIsAffine = true; + return ret; +} + +const Transformation Transformation::projectivityGI4P( + const std::vector<Coordinate>& FromPoints, + const std::vector<Coordinate>& ToPoints, + bool& valid ) +{ + // construct the (generically) unique projectivity that transforms 4 given + // point into 4 other given points; i.e. it depends on the coordinates of + // a total of 8 points. This actually amounts in solving an underdetermined + // homogeneous linear system. + + double + row0[13], row1[13], row2[13], row3[13], row4[13], row5[13], row6[13], row7[13], + row8[13], row9[13], row10[13], row11[13]; + + double *matrix[12] = {row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11}; + + double solution[13]; + int scambio[13]; + + assert (FromPoints.size() == 4); + assert (ToPoints.size() == 4); + + // fill in the matrix elements + for ( int i = 0; i < 12; i++ ) + { + for ( int j = 0; j < 13; j++ ) + { + matrix[i][j] = 0.0; + } + } + + for ( int i = 0; i < 4; i++ ) + { + Coordinate p = FromPoints[i]; + Coordinate q = ToPoints[i]; + matrix[i][0] = matrix[4+i][3] = matrix[8+i][6] = 1.0; + matrix[i][1] = matrix[4+i][4] = matrix[8+i][7] = p.x; + matrix[i][2] = matrix[4+i][5] = matrix[8+i][8] = p.y; + matrix[i][9+i] = -1.0; + matrix[4+i][9+i] = -q.x; + matrix[8+i][9+i] = -q.y; + } + + Transformation ret; + valid = true; + if ( ! GaussianElimination( matrix, 12, 13, scambio ) ) + { valid = false; return ret; } + + // fine della fase di eliminazione + BackwardSubstitution( matrix, 12, 13, scambio, solution ); + + // now we can build the 3x3 transformation matrix; remember that + // unknowns from 9 to 13 are just multiplicators that we don't need here + + int k = 0; + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + ret.mdata[i][j] = solution[k++]; + } + } + + ret.mIsHomothety = ret.mIsAffine = false; + return ret; +} + +const Transformation Transformation::castShadow( + const Coordinate& lightsrc, const LineData& l ) +{ + // first deal with the line l, I need to find an appropriate reflection + // that transforms l onto the x-axis + + Coordinate d = l.dir(); + Coordinate a = l.a; + double k = d.length(); + if ( d.x < 0 ) k *= -1; // for numerical stability + Coordinate w = d + Coordinate( k, 0 ); + // w /= w.length(); + // w defines a Householder transformation, but we don't need to normalize + // it here. + // warning: this w is the orthogonal of the w of the textbooks! + // this is fine for us since in this way it indicates the line direction + Coordinate ra = Coordinate ( a.x + w.y*a.y/(2*w.x), a.y/2 ); + Transformation sym = lineReflection ( LineData( ra, ra + w ) ); + + // in the new coordinates the line is the x-axis + // I must transform the point + + Coordinate modlightsrc = sym.apply ( lightsrc ); + Transformation ret = identity(); + // parameter t indicates the distance of the light source from + // the plane of the drawing. A negative value means that the light + // source is behind the plane. + double t = -1.0; + // double t = -modlightsrc.y; <-- this gives the old transformation! + double e = modlightsrc.y - t; + ret.mdata[0][0] = e; + ret.mdata[0][2] = -1; + ret.mdata[1][1] = e; + ret.mdata[1][2] = -modlightsrc.x; + ret.mdata[2][2] = -t; + + ret.mIsHomothety = ret.mIsAffine = false; + return sym*ret*sym; +// return translation( t )*ret*translation( -t ); +} + +const Transformation Transformation::projectiveRotation( + double alpha, const Coordinate& d, const Coordinate& t ) +{ + Transformation ret; + double cosalpha = cos( alpha ); + double sinalpha = sin( alpha ); + ret.mdata[0][0] = cosalpha; + ret.mdata[1][1] = cosalpha*d.x*d.x + d.y*d.y; + ret.mdata[0][1] = -sinalpha*d.x; + ret.mdata[1][0] = sinalpha*d.x; + ret.mdata[0][2] = -sinalpha*d.y; + ret.mdata[2][0] = sinalpha*d.y; + ret.mdata[1][2] = cosalpha*d.x*d.y - d.x*d.y; + ret.mdata[2][1] = cosalpha*d.x*d.y - d.x*d.y; + ret.mdata[2][2] = cosalpha*d.y*d.y + d.x*d.x; + + ret.mIsHomothety = ret.mIsAffine = false; + return translation( t )*ret*translation( -t ); +} + +const Coordinate Transformation::apply( const double x0, + const double x1, + const double x2) const +{ + double phom[3] = {x0, x1, x2}; + double rhom[3] = {0., 0., 0.}; + + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + rhom[i] += mdata[i][j]*phom[j]; + } + } + + if (rhom[0] == 0.) + return Coordinate::invalidCoord(); + + return Coordinate (rhom[1]/rhom[0], rhom[2]/rhom[0]); +} + +const Coordinate Transformation::apply( const Coordinate& p ) const +{ + return apply( 1., p.x, p.y ); +// double phom[3] = {1., p.x, p.y}; +// double rhom[3] = {0., 0., 0.}; +// +// for (int i = 0; i < 3; i++) +// { +// for (int j = 0; j < 3; j++) +// { +// rhom[i] += mdata[i][j]*phom[j]; +// } +// } +// +// if (rhom[0] == 0.) +// return Coordinate::invalidCoord(); +// +// return Coordinate (rhom[1]/rhom[0], rhom[2]/rhom[0]); +} + +const Coordinate Transformation::apply0( const Coordinate& p ) const +{ + return apply( 0., p.x, p.y ); +} + +const Transformation Transformation::rotation( double alpha, const Coordinate& center ) +{ + Transformation ret = identity(); + + double x = center.x; + double y = center.y; + + double cosalpha = cos( alpha ); + double sinalpha = sin( alpha ); + + ret.mdata[1][1] = ret.mdata[2][2] = cosalpha; + ret.mdata[1][2] = -sinalpha; + ret.mdata[2][1] = sinalpha; + ret.mdata[1][0] = x - ret.mdata[1][1]*x - ret.mdata[1][2]*y; + ret.mdata[2][0] = y - ret.mdata[2][1]*x - ret.mdata[2][2]*y; + + // this is already set in the identity() constructor, but just for + // clarity.. + ret.mIsHomothety = ret.mIsAffine = true; + + return ret; +} + +bool Transformation::isHomothetic() const +{ + return mIsHomothety; +} + +bool Transformation::isAffine() const +{ + return mIsAffine; +} + +/* + *mp: + * this function has the property that it changes sign if computed + * on two points that lie on either sides with respect to the critical + * line (this is the line that goes to the line at infinity). + * For affine transformations the result has always the same sign. + * NOTE: the result is *not* invariant under rescaling of all elements + * of the transformation matrix. + * The typical use is to determine whether a segment is transformed + * into a segment or a couple of half-lines. + */ + +double Transformation::getProjectiveIndicator( const Coordinate& c ) const +{ + return mdata[0][0] + mdata[0][1]*c.x + mdata[0][2]*c.y; +} + +// assuming that this is an affine transformation, return its +// determinant. What is really important here is just the sign +// of the determinant. +double Transformation::getAffineDeterminant() const +{ + return mdata[1][1]*mdata[2][2] - mdata[1][2]*mdata[2][1]; +} + +// this assumes that the 2x2 affine part of the matrix is of the +// form [ cos a, sin a; -sin a, cos a] or a multiple +double Transformation::getRotationAngle() const +{ + return atan2( mdata[1][2], mdata[1][1] ); +} + +const Coordinate Transformation::apply2by2only( const Coordinate& p ) const +{ + double x = p.x; + double y = p.y; + double nx = mdata[1][1]*x + mdata[1][2]*y; + double ny = mdata[2][1]*x + mdata[2][2]*y; + return Coordinate( nx, ny ); +} + +double Transformation::data( int r, int c ) const +{ + return mdata[r][c]; +} + +const Transformation Transformation::inverse( bool& valid ) const +{ + Transformation ret; + + valid = Invert3by3matrix( mdata, ret.mdata ); + + // the inverse of a homothety is a homothety, same for affinities.. + ret.mIsHomothety = mIsHomothety; + ret.mIsAffine = mIsAffine; + + return ret; +} + +Transformation::Transformation() +{ + // this is the constructor used by the static Transformation + // creation functions, so mIsHomothety is in general false + mIsHomothety = mIsAffine = false; + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + mdata[i][j] = ( i == j ) ? 1 : 0; +} + +Transformation::~Transformation() +{ +} + +double Transformation::apply( double length ) const +{ + assert( isHomothetic() ); + double det = mdata[1][1]*mdata[2][2] - + mdata[1][2]*mdata[2][1]; + return sqrt( fabs( det ) ) * length; +} + +Transformation::Transformation( double data[3][3], bool ishomothety ) + : mIsHomothety( ishomothety ) +{ + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + mdata[i][j] = data[i][j]; + + //mp: a test for affinity is used to initialize mIsAffine... + mIsAffine = false; + if ( fabs(mdata[0][1]) + fabs(mdata[0][2]) < 1e-8 * fabs(mdata[0][0]) ) + mIsAffine = true; +} + +bool operator==( const Transformation& lhs, const Transformation& rhs ) +{ + for ( int i = 0; i < 3; ++i ) + for ( int j = 0; j < 3; ++j ) + if ( lhs.data( i, j ) != rhs.data( i, j ) ) + return false; + return true; +} + +const Transformation Transformation::similitude( + const Coordinate& center, double theta, double factor ) +{ + //kdDebug() << k_funcinfo << "theta: " << theta << " factor: " << factor << endl; + Transformation ret; + ret.mIsHomothety = true; + double costheta = cos( theta ); + double sintheta = sin( theta ); + ret.mdata[0][0] = 1; + ret.mdata[0][1] = 0; + ret.mdata[0][2] = 0; + ret.mdata[1][0] = ( 1 - factor*costheta )*center.x + factor*sintheta*center.y; + ret.mdata[1][1] = factor*costheta; + ret.mdata[1][2] = -factor*sintheta; + ret.mdata[2][0] = -factor*sintheta*center.x + ( 1 - factor*costheta )*center.y; + ret.mdata[2][1] = factor*sintheta; + ret.mdata[2][2] = factor*costheta; + // fails for factor == infinity + //assert( ( ret.apply( center ) - center ).length() < 1e-5 ); + ret.mIsHomothety = ret.mIsAffine = true; + return ret; +} diff --git a/kig/misc/kigtransform.h b/kig/misc/kigtransform.h new file mode 100644 index 00000000..252a0f72 --- /dev/null +++ b/kig/misc/kigtransform.h @@ -0,0 +1,190 @@ +/** + This file is part of Kig, a KDE program for Interactive Geometry... + Copyright (C) 2002 Maurizio Paolini <paolini@dmf.unicatt.it> + Copyright (C) 2003 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 +**/ + +#ifndef KIG_MISC_KIGTRANSFORM_H +#define KIG_MISC_KIGTRANSFORM_H + +#include "coordinate.h" +#include <vector> + +class LineData; + +/** + * Class representing a transformation. More specifically, this class + * represents a pretty generic 2-dimensional transformation. Various + * common transformations can be used. Construct a Transformation by + * using one of its static members, and use it either with its + * Transformation::apply method, or the ObjectImp::transform method. + */ +class Transformation +{ + double mdata[3][3]; + bool mIsHomothety; + bool mIsAffine; + Transformation(); +public: + ~Transformation(); + Transformation( double data[3][3], bool ishomothety ); + + /** + * Apply this Tranformation. Apply this transformation to the + * Coordinate c. Can return an invalid Coordinate. + * apply0 assumes that c indicates a point at infinity, having + * [0, c.x, c.y] as homogeneous coordinates + */ + const Coordinate apply( const double x0, const double x1, const double x2 ) const; + const Coordinate apply( const Coordinate& c ) const; + const Coordinate apply0( const Coordinate& c ) const; + + /** + * Returns whether this is a homothetic (affine) transformation. + */ + bool isHomothetic() const; + bool isAffine() const; + double getProjectiveIndicator( const Coordinate& c ) const; + double getAffineDeterminant() const; + double getRotationAngle() const; + const Coordinate apply2by2only( const Coordinate& c ) const; + /** + * \ifnot creating-python-scripting-doc + * a homothetic transformation maintains the ratio's of lengths. + * This means that every length is multiplied by a fixed number when + * it is projected... This function does that calculation for + * you.. + * \endif + */ + double apply( double length ) const; + double data( int r, int c ) const; + /** + * The inverse Transformation. Returns the inverse Transformation + * of this Transformation. + */ + const Transformation inverse( bool& valid ) const; + + /** + * Identity. Returns the Identity Transformation, i.e. a + * Transformation that doesn't do anything. + */ + static const Transformation identity(); + /** + * Scaling over Point. Returns a Transformation that scales points + * by a certain factor with relation to a center point. + */ + static const Transformation scalingOverPoint( double factor, const Coordinate& center = Coordinate() ); + /** + * Scaling over Line. Returns a Transformation that scales points + * by a certain factor with relation to a line. Note: This is not a + * homothetic transformation. + */ + static const Transformation scalingOverLine( double factor, const LineData& l ); + /** + * Translation. Returns a Translation by a vector c. + */ + static const Transformation translation( const Coordinate& c ); + /** + * Rotation. Returns a Rotation by a certain angle, around a + * certain center. + */ + static const Transformation rotation( double angle, const Coordinate& center = Coordinate() ); + /** + * Point Reflection. Returns a reflection over a point + * \note This equals scaling( -1, c ); + */ + static const Transformation pointReflection( const Coordinate& c ); + /** + * Line Reflection. Returns a reflection over a line + * \note This equals scaling( -1, l ); + */ + static const Transformation lineReflection( const LineData& l ); + /** + * Harmonic Homology. Returns a Transformation that transforms points in + * such a way that it appears to cast a shadow, given a certain + * light source (center), and a line (axis) indicating a plane. + */ + static const Transformation harmonicHomology( const Coordinate& center, + const LineData& axis ); + /** + * Affinity given the image of 3 points. Returns the unique + * affinity that transforms 3 given points into 3 given points. + */ + static const Transformation affinityGI3P( + const std::vector<Coordinate>& FromPoints, + const std::vector<Coordinate>& ToPoints, + bool& valid ); + /** + * Projectivity given the image of 4 points. Returns the unique + * projectivity that transforms 4 given points into 4 given points. + */ + static const Transformation projectivityGI4P( + const std::vector<Coordinate>& FromPoints, + const std::vector<Coordinate>& ToPoints, + bool& valid ); + /** + * Cast Shadow. Returns a Transformation that transforms points in + * such a way that it appears to cast a shadow, given a certain + * light source, and a line indicating a plane. + */ + static const Transformation castShadow( const Coordinate& ls, + const LineData& d ); + /** + * Projective Rotation. This is really only a test example of a + * projective non-affine transformation... + */ + static const Transformation projectiveRotation( double alpha, + const Coordinate& d, + const Coordinate& t ); + + /** + * Similitude. Sequence of a rotation and a scaling with relation + * to a certain center. + */ + static const Transformation similitude( + const Coordinate& center, double theta, double factor ); + + /** + * Sequence. This creates a Transformation that executes first + * transformation b, and then a. + */ + friend const Transformation operator*( const Transformation& a, const Transformation& b ); + + /** + * Equality. Tests two Transformation's for equality. + */ + friend bool operator==( const Transformation& lhs, const Transformation& rhs ); +}; + +const Transformation operator*( const Transformation&, const Transformation& ); +bool operator==( const Transformation& lhs, const Transformation& rhs ); + +// enum tWantArgsResult { tComplete, tNotComplete, tNotGood }; + +// Transformation getProjectiveTransformation( +// int transformationsnum, Object *mtransformations[], +// bool& valid ); + +// tWantArgsResult WantTransformation ( Objects::const_iterator& i, +// const Objects& os ); + +// QString getTransformMessage ( const Objects& os, const Object *o ); + +// bool isHomoteticTransformation ( double transformation[3][3] ); + +#endif // KIG_MISC_KIGTRANSFORM_H diff --git a/kig/misc/lists.cc b/kig/misc/lists.cc new file mode 100644 index 00000000..e93700a1 --- /dev/null +++ b/kig/misc/lists.cc @@ -0,0 +1,389 @@ +// Copyright (C) 2003 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 "lists.h" + +#include "object_constructor.h" +#include "guiaction.h" +#include "object_hierarchy.h" +#include "../kig/kig_part.h" + +#include "config.h" + +#include <klocale.h> +#include <kmessagebox.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qdom.h> +#include <qregexp.h> +#include <algorithm> +using namespace std; + +template<typename T> +void vect_remove( std::vector<T>& v, const T& t ) +{ + typename std::vector<T>::iterator new_end = std::remove( v.begin(), v.end(), t ); + v.erase( new_end, v.end() ); +} + +GUIActionList* GUIActionList::instance() +{ + static GUIActionList l; + return &l; +} + +GUIActionList::~GUIActionList() +{ + for ( avectype::iterator i = mactions.begin(); i != mactions.end(); ++i ) + delete *i; +} + +GUIActionList::GUIActionList() +{ +} + +void GUIActionList::regDoc( KigPart* d ) +{ + mdocs.insert( d ); +} + +void GUIActionList::unregDoc( KigPart* d ) +{ + mdocs.erase( d ); +} + +void GUIActionList::add( const std::vector<GUIAction*>& a ) +{ + copy( a.begin(), a.end(), inserter( mactions, mactions.begin() ) ); + for ( dvectype::iterator i = mdocs.begin(); i != mdocs.end(); ++i ) + { + KigPart::GUIUpdateToken t = (*i)->startGUIActionUpdate(); + for ( uint j = 0; j < a.size(); ++j ) + (*i)->actionAdded( a[j], t ); + (*i)->endGUIActionUpdate( t ); + }; +} + +void GUIActionList::add( GUIAction* a ) +{ + mactions.insert( a ); + for ( dvectype::iterator i = mdocs.begin(); i != mdocs.end(); ++i ) + { + KigPart::GUIUpdateToken t = (*i)->startGUIActionUpdate(); + (*i)->actionAdded( a, t ); + (*i)->endGUIActionUpdate( t ); + }; +} + +void GUIActionList::remove( const std::vector<GUIAction*>& a ) +{ + for ( uint i = 0; i < a.size(); ++i ) + { + mactions.erase( a[i] ); + }; + for ( dvectype::iterator i = mdocs.begin(); i != mdocs.end(); ++i ) + { + KigPart::GUIUpdateToken t = (*i)->startGUIActionUpdate(); + for ( uint j = 0; j < a.size(); ++j ) + (*i)->actionRemoved( a[j], t ); + (*i)->endGUIActionUpdate( t ); + }; + delete_all( a.begin(), a.end() ); +} + +void GUIActionList::remove( GUIAction* a ) +{ + mactions.erase( a ); + for ( dvectype::iterator i = mdocs.begin(); i != mdocs.end(); ++i ) + { + KigPart::GUIUpdateToken t = (*i)->startGUIActionUpdate(); + (*i)->actionRemoved( a, t ); + (*i)->endGUIActionUpdate( t ); + }; + delete a; +} + +ObjectConstructorList::ObjectConstructorList() +{ +} + +ObjectConstructorList::~ObjectConstructorList() +{ + for ( vectype::iterator i = mctors.begin(); i != mctors.end(); ++i ) + delete *i; +} + +ObjectConstructorList* ObjectConstructorList::instance() +{ + static ObjectConstructorList s; + return &s; +} + +ObjectConstructorList::vectype ObjectConstructorList::ctorsThatWantArgs( + const std::vector<ObjectCalcer*>& os, const KigDocument& d, + const KigWidget& w, bool co ) const +{ + vectype ret; + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + int r = (*i)->wantArgs( os, d, w ); + if ( r == ArgsParser::Complete || ( !co && r == ArgsParser::Valid ) ) + ret.push_back( *i ); + }; + return ret; +} + +void ObjectConstructorList::remove( ObjectConstructor* a ) +{ + vect_remove( mctors, a ); + delete a; +} + +void ObjectConstructorList::add( ObjectConstructor* a ) +{ + mctors.push_back( a ); +} + +Macro::Macro( GUIAction* a, MacroConstructor* c ) + : action( a ), ctor( c ) +{ +} + +bool operator==( const Macro& l, const Macro& r ) +{ + return ( l.action->descriptiveName() == r.action->descriptiveName() ) && + ( l.action->description() == r.action->description() ) && + ( l.action->iconFileName() == r.action->iconFileName() ); +} + +MacroList::MacroList() +{ +} + +MacroList::~MacroList() +{ + std::vector<GUIAction*> actions; + std::vector<ObjectConstructor*> ctors; + for ( vectype::iterator i = mdata.begin(); i != mdata.end(); ++i ) + { + Macro* m = *i; + GUIAction* a = m->action; + actions.push_back( a ); + ObjectConstructor* c = m->ctor; + ctors.push_back( c ); + delete m; + }; + mdata.clear(); + GUIActionList::instance()->remove( actions ); + for ( uint i = 0; i < ctors.size(); ++i ) + ObjectConstructorList::instance()->remove( ctors[i] ); +} + +MacroList* MacroList::instance() +{ + static MacroList t; + return &t; +} + +void MacroList::add( const std::vector<Macro*>& ms ) +{ + copy( ms.begin(), ms.end(), back_inserter( mdata ) ); + std::vector<GUIAction*> acts; + for ( uint i = 0; i < ms.size(); ++i ) + { + ObjectConstructorList::instance()->add( ms[i]->ctor ); + acts.push_back( ms[i]->action ); + }; + GUIActionList::instance()->add( acts ); +} + +void MacroList::add( Macro* m ) +{ + mdata.push_back( m ); + ObjectConstructorList::instance()->add( m->ctor ); + GUIActionList::instance()->add( m->action ); +} + +void MacroList::remove( Macro* m ) +{ + GUIAction* a = m->action; + ObjectConstructor* c = m->ctor; + mdata.erase( std::remove( mdata.begin(), mdata.end(), m ), + mdata.end() ); + delete m; + GUIActionList::instance()->remove( a ); + ObjectConstructorList::instance()->remove( c ); +} + +const MacroList::vectype& MacroList::macros() const +{ + return mdata; +} + +Macro::~Macro() +{ +} + +bool MacroList::save( Macro* m, const QString& f ) +{ + std::vector<Macro*> ms; + ms.push_back( m ); + return save( ms, f ); +} + +bool MacroList::save( const std::vector<Macro*>& ms, const QString& f ) +{ + QDomDocument doc( "KigMacroFile" ); + + QDomElement docelem = doc.createElement( "KigMacroFile" ); + docelem.setAttribute( "Version", KIGVERSION ); + docelem.setAttribute( "Number", ms.size() ); + + for ( uint i = 0; i < ms.size(); ++i ) + { + MacroConstructor* ctor = ms[i]->ctor; + + QDomElement macroelem = doc.createElement( "Macro" ); + + // name + QDomElement nameelem = doc.createElement( "Name" ); + nameelem.appendChild( doc.createTextNode( ctor->descriptiveName() ) ); + macroelem.appendChild( nameelem ); + + // desc + QDomElement descelem = doc.createElement( "Description" ); + descelem.appendChild( doc.createTextNode( ctor->description() ) ); + macroelem.appendChild( descelem ); + + // icon + QCString icon = ctor->iconFileName( true ); + if ( !icon.isNull() ) + { + QDomElement descelem = doc.createElement( "IconFileName" ); + descelem.appendChild( doc.createTextNode( icon ) ); + macroelem.appendChild( descelem ); + } + + // data + QDomElement hierelem = doc.createElement( "Construction" ); + ctor->hierarchy().serialize( hierelem, doc ); + macroelem.appendChild( hierelem ); + + docelem.appendChild( macroelem ); + }; + + doc.appendChild( docelem ); + + QFile file( f ); + if ( ! file.open( IO_WriteOnly ) ) + return false; + QTextStream stream( &file ); + stream << doc.toCString(); + return true; +} + +bool MacroList::load( const QString& f, std::vector<Macro*>& ret, const KigPart& kdoc ) +{ + QFile file( f ); + if ( ! file.open( IO_ReadOnly ) ) + { + KMessageBox::sorry( 0, i18n( "Could not open macro file '%1'" ).arg( f ) ); + return false; + } + QDomDocument doc( "KigMacroFile" ); + if ( !doc.setContent( &file ) ) + { + KMessageBox::sorry( 0, i18n( "Could not open macro file '%1'" ).arg( f ) ); + return false; + } + file.close(); + QDomElement main = doc.documentElement(); + + if ( main.tagName() == "KigMacroFile" ) + return loadNew( main, ret, kdoc ); + else + { + KMessageBox::detailedSorry( + 0, i18n( "Kig cannot open the macro file \"%1\"." ).arg( f ), + i18n( "This file was created by a very old Kig version (pre-0.4). " + "Support for this format has been removed from recent Kig versions. " + "You can try to import this macro using a previous Kig version " + "(0.4 to 0.6) and then export it again in the new format." ), + i18n( "Not Supported" ) ); + return false; + } +} + +bool MacroList::loadNew( const QDomElement& docelem, std::vector<Macro*>& ret, const KigPart& ) +{ + bool sok = true; + // unused.. +// int number = docelem.attribute( "Number" ).toInt( &sok ); + if ( ! sok ) return false; + + QString version = docelem.attribute( "Version" ); +// QRegExp re( "(\\d+)\\.(\\d+)\\.(\\d+)" ); +// re.match( version ); + // unused.. +// int major = re.cap( 1 ).toInt( &sok ); +// int minor = re.cap( 2 ).toInt( &sok ); +// int mminor = re.cap( 3 ).toInt( &sok ); +// if ( ! sok ) return false; + + int unnamedindex = 1; + QString tmp; + + for ( QDomElement macroelem = docelem.firstChild().toElement(); + ! macroelem.isNull(); macroelem = macroelem.nextSibling().toElement() ) + { + QString name, description; + ObjectHierarchy* hierarchy = 0; + QCString actionname, iconfile; + if ( macroelem.tagName() != "Macro" ) continue; // forward compat ? + for ( QDomElement dataelem = macroelem.firstChild().toElement(); + ! dataelem.isNull(); dataelem = dataelem.nextSibling().toElement() ) + { + if ( dataelem.tagName() == "Name" ) + name = dataelem.text(); + else if ( dataelem.tagName() == "Description" ) + description = dataelem.text(); + else if ( dataelem.tagName() == "Construction" ) + hierarchy = ObjectHierarchy::buildSafeObjectHierarchy( dataelem, tmp ); + else if ( dataelem.tagName() == "ActionName" ) + actionname = dataelem.text().latin1(); + else if ( dataelem.tagName() == "IconFileName" ) + iconfile = dataelem.text().latin1(); + else continue; + }; + assert( hierarchy ); + // if the macro has no name, we give it a bogus name... + if ( name.isEmpty() ) + name = i18n( "Unnamed Macro #%1" ).arg( unnamedindex++ ); + MacroConstructor* ctor = + new MacroConstructor( *hierarchy, i18n( name.latin1() ), i18n( description.latin1() ), iconfile ); + delete hierarchy; + GUIAction* act = new ConstructibleAction( ctor, actionname ); + Macro* macro = new Macro( act, ctor ); + ret.push_back( macro ); + }; + return true; +} + +const ObjectConstructorList::vectype& ObjectConstructorList::constructors() const +{ + return mctors; +} diff --git a/kig/misc/lists.h b/kig/misc/lists.h new file mode 100644 index 00000000..a3f97f1d --- /dev/null +++ b/kig/misc/lists.h @@ -0,0 +1,170 @@ +// Copyright (C) 2003 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. + +#ifndef KIG_MISC_LISTS_H +#define KIG_MISC_LISTS_H + +#include <vector> +#include <set> + +class GUIAction; +class ObjectConstructor; +class MacroConstructor; +class KigDocument; +class KigPart; +class KigWidget; +class QString; +class QDomElement; +class ObjectCalcer; + +/** + * List of GUIActions for the parts to show. Note that the list owns + * the actions it receives.. + */ +class GUIActionList +{ +public: + typedef std::set<GUIAction*> avectype; + typedef std::set<KigPart*> dvectype; +private: + avectype mactions; + dvectype mdocs; + GUIActionList(); + ~GUIActionList(); +public: + static GUIActionList* instance(); + const avectype& actions() const { return mactions; } + + /** + * register this document, so that it receives notifications for + * added and removed actions.. + */ + void regDoc( KigPart* d ); + void unregDoc( KigPart* d ); + + void add( GUIAction* a ); + void add( const std::vector<GUIAction*>& a ); + void remove( GUIAction* a ); + void remove( const std::vector<GUIAction*>& a ); +}; + +/** + * The list of object constructors for use in various places, e.g. RMB + * menu's. Note that the list owns the ctors it gets.. + */ +class ObjectConstructorList +{ +public: + typedef std::vector<ObjectConstructor*> vectype; +private: + vectype mctors; + ObjectConstructorList(); + ~ObjectConstructorList(); +public: + static ObjectConstructorList* instance(); + void add( ObjectConstructor* a ); + void remove( ObjectConstructor* a ); + vectype ctorsThatWantArgs( const std::vector<ObjectCalcer*>&, const KigDocument&, + const KigWidget&, bool completeOnly = false + ) const; + const vectype& constructors() const; +}; + +/** + * this is just a simple data struct. doesn't have any functionality + * of its own.. + */ +class Macro +{ +public: + GUIAction* action; + MacroConstructor* ctor; + Macro( GUIAction* a, MacroConstructor* c ); + ~Macro(); +}; + +/** + * a simply equality operator for Macro class. + */ +bool operator==( const Macro& l, const Macro& r ); + +/** + * This class keeps a list of all macro's, and allows them to be + * easily accessed, added etc. Macro objects are owned by this + * list.. + */ +class MacroList +{ +public: + typedef std::vector<Macro*> vectype; +private: + vectype mdata; + MacroList(); + ~MacroList(); +public: + /** + * MacroList is a singleton + */ + static MacroList* instance(); + + /** + * Add a Macro \p m . MacroList takes care of adding the action and + * ctor in the relevant places.. + */ + void add( Macro* m ); + /** + * Add the Macro's \p ms. MacroList takes care of adding the action + * and ctor in the relevant places.. + */ + void add( const vectype& ms ); + + /** + * Remove macro \p m . Macrolist takes care of deleting everything, and + * unregistering the action and ctor from the relevant places.. + */ + void remove( Macro* m ); + + /** + * Save macro \p m to file \p f .. + */ + bool save( Macro* m, const QString& f ); + /** + * Save macros \p ms to file \p f .. + */ + bool save( const vectype& ms, const QString& f ); + + /** + * load macro's from file \p f .. + * note that this just returns the loaded macro's, and doesn't add + * them to the various lists. Use add() if you want + * that behaviour.. + * The fact that this functions requires a KigPart argument is + * semantically incorrect, but i haven't been able to work around + * it.. + */ + bool load( const QString& f, vectype& ret, const KigPart& ); + + /** + * get access to the list of macro's.. + */ + const vectype& macros() const; + +private: + bool loadNew( const QDomElement& docelem, std::vector<Macro*>& ret, const KigPart& ); +}; + +#endif diff --git a/kig/misc/object_constructor.cc b/kig/misc/object_constructor.cc new file mode 100644 index 00000000..5634d0d2 --- /dev/null +++ b/kig/misc/object_constructor.cc @@ -0,0 +1,609 @@ +// 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 "object_constructor.h" + +#include "argsparser.h" +#include "kigpainter.h" +#include "guiaction.h" + +#include "../kig/kig_part.h" +#include "../kig/kig_view.h" + +#include "../objects/object_holder.h" +#include "../objects/object_drawer.h" +#include "../objects/object_type.h" +#include "../objects/other_type.h" +#include "../objects/object_imp.h" +#include "../objects/bogus_imp.h" +#include "../objects/line_imp.h" +#include "../objects/circle_imp.h" +#include "../objects/point_imp.h" + +#include "../modes/construct_mode.h" + +#include <qpen.h> + +#include <klocale.h> + +#include <algorithm> +#include <functional> + +const QString StandardConstructorBase::descriptiveName() const +{ + return i18n( mdescname ); +} + +const QString StandardConstructorBase::description() const +{ + return i18n( mdesc ); +} + +const QCString StandardConstructorBase::iconFileName( const bool ) const +{ + return miconfile; +} + +const bool StandardConstructorBase::isAlreadySelectedOK( const std::vector<ObjectCalcer*>&, const int& ) const +{ + return false; +} + +StandardConstructorBase::StandardConstructorBase( + const char* descname, const char* desc, + const char* iconfile, const ArgsParser& parser ) + : mdescname( descname ), + mdesc( desc ), + miconfile( iconfile ), + margsparser( parser ) +{ +} + +const int StandardConstructorBase::wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument&, + const KigWidget& ) const +{ + return margsparser.check( os ); +} + +void StandardConstructorBase::handleArgs( + const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& v ) const +{ + std::vector<ObjectHolder*> bos = build( os, d.document(), v ); + for ( std::vector<ObjectHolder*>::iterator i = bos.begin(); + i != bos.end(); ++i ) + { + (*i)->calc( d.document() ); + } + + d.addObjects( bos ); +} + +void StandardConstructorBase::handlePrelim( + KigPainter& p, const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& + ) const +{ + assert ( margsparser.check( os ) != ArgsParser::Invalid ); + std::vector<ObjectCalcer*> args = margsparser.parse( os ); + p.setBrushStyle( Qt::NoBrush ); + p.setBrushColor( Qt::red ); + p.setPen( QPen ( Qt::red, 1) ); + p.setWidth( -1 ); // -1 means the default width for the object being + // drawn.. + + ObjectDrawer drawer( Qt::red ); + drawprelim( drawer, p, args, d ); +} + +SimpleObjectTypeConstructor::SimpleObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile ) + : StandardConstructorBase( descname, desc, iconfile, + t->argsParser() ), + mtype( t ) +{ +} + +SimpleObjectTypeConstructor::~SimpleObjectTypeConstructor() +{ +} + +void SimpleObjectTypeConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& doc ) const +{ + Args args; + using namespace std; + transform( parents.begin(), parents.end(), + back_inserter( args ), mem_fun( &ObjectCalcer::imp ) ); + ObjectImp* data = mtype->calc( args, doc ); + drawer.draw( *data, p, true ); + delete data; +} + +std::vector<ObjectHolder*> SimpleObjectTypeConstructor::build( + const std::vector<ObjectCalcer*>& os, KigDocument&, KigWidget& ) const +{ + ObjectTypeCalcer* calcer = new ObjectTypeCalcer( mtype, os ); + ObjectHolder* h = new ObjectHolder( calcer ); + std::vector<ObjectHolder*> ret; + ret.push_back( h ); + return ret; +} + +StandardConstructorBase::~StandardConstructorBase() +{ +} + +MultiObjectTypeConstructor::MultiObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile, + const std::vector<int>& params ) + : StandardConstructorBase( descname, desc, iconfile, mparser ), + mtype( t ), mparams( params ), + mparser( t->argsParser().without( IntImp::stype() ) ) +{ +} + +MultiObjectTypeConstructor::MultiObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile, + int a, int b, int c, int d ) + : StandardConstructorBase( descname, desc, iconfile, mparser ), + mtype( t ), mparams(), + mparser( t->argsParser().without( IntImp::stype() ) ) +{ + mparams.push_back( a ); + mparams.push_back( b ); + if ( c != -999 ) mparams.push_back( c ); + if ( d != -999 ) mparams.push_back( d ); +} + +MultiObjectTypeConstructor::~MultiObjectTypeConstructor() +{ +} + +void MultiObjectTypeConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& doc ) const +{ + Args args; + using namespace std; + transform( parents.begin(), parents.end(), + back_inserter( args ), mem_fun( &ObjectCalcer::imp ) ); + + for ( vector<int>::const_iterator i = mparams.begin(); i != mparams.end(); ++i ) + { + IntImp param( *i ); + args.push_back( ¶m ); + ObjectImp* data = mtype->calc( args, doc ); + drawer.draw( *data, p, true ); + delete data; + args.pop_back(); + }; +} + +std::vector<ObjectHolder*> MultiObjectTypeConstructor::build( + const std::vector<ObjectCalcer*>& os, KigDocument&, KigWidget& ) const +{ + std::vector<ObjectHolder*> ret; + for ( std::vector<int>::const_iterator i = mparams.begin(); + i != mparams.end(); ++i ) + { + ObjectConstCalcer* d = new ObjectConstCalcer( new IntImp( *i ) ); + + std::vector<ObjectCalcer*> args( os ); + args.push_back( d ); + + ret.push_back( new ObjectHolder( new ObjectTypeCalcer( mtype, args ) ) ); + }; + return ret; +} + +MergeObjectConstructor::~MergeObjectConstructor() +{ + for ( vectype::iterator i = mctors.begin(); i != mctors.end(); ++i ) + delete *i; +} + +MergeObjectConstructor::MergeObjectConstructor( + const char* descname, const char* desc, const char* iconfilename ) + : ObjectConstructor(), mdescname( descname ), mdesc( desc ), + miconfilename( iconfilename ), mctors() +{ +} + +ObjectConstructor::~ObjectConstructor() +{ +} + +void MergeObjectConstructor::merge( ObjectConstructor* e ) +{ + mctors.push_back( e ); +} + +const QString MergeObjectConstructor::descriptiveName() const +{ + return i18n( mdescname ); +} + +const QString MergeObjectConstructor::description() const +{ + return i18n( mdesc ); +} + +const QCString MergeObjectConstructor::iconFileName( const bool ) const +{ + return miconfilename; +} + +const bool MergeObjectConstructor::isAlreadySelectedOK( const std::vector<ObjectCalcer*>&, const int& ) const +{ + return false; +} + +const int MergeObjectConstructor::wantArgs( + const std::vector<ObjectCalcer*>& os, const KigDocument& d, const KigWidget& v ) const +{ + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + int w = (*i)->wantArgs( os, d, v ); + if ( w != ArgsParser::Invalid ) return w; + }; + return ArgsParser::Invalid; +} + +void MergeObjectConstructor::handleArgs( + const std::vector<ObjectCalcer*>& os, KigPart& d, KigWidget& v ) const +{ + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + int w = (*i)->wantArgs( os, d.document(), v ); + if ( w == ArgsParser::Complete ) + { + (*i)->handleArgs( os, d, v ); + return; + }; + }; + assert( false ); +} + +void MergeObjectConstructor::handlePrelim( + KigPainter& p, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v ) const +{ + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + int w = (*i)->wantArgs( sel, d, v ); + if ( w != ArgsParser::Invalid ) + { + (*i)->handlePrelim( p, sel, d, v ); + return; + }; + }; +} + +QString StandardConstructorBase::useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument&, const KigWidget& ) const +{ + using namespace std; + Args args; + transform( sel.begin(), sel.end(), back_inserter( args ), mem_fun( &ObjectCalcer::imp ) ); + + std::string ret = margsparser.usetext( o.imp(), args ); + if ( ret.empty() ) return QString::null; + return i18n( ret.c_str() ); +} + +QString StandardConstructorBase::selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument&, + const KigWidget& ) const +{ + using namespace std; + Args args; + transform( sel.begin(), sel.end(), back_inserter( args ), mem_fun( &ObjectCalcer::imp ) ); + + std::string ret = margsparser.selectStatement( args ); + if ( ret.empty() ) return QString::null; + return i18n( ret.c_str() ); +} + +QString MergeObjectConstructor::useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v ) const +{ + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + std::vector<ObjectCalcer*> args( sel ); + int w = (*i)->wantArgs( args, d, v ); + if ( w != ArgsParser::Invalid ) return (*i)->useText( o, sel, d, v ); + }; + return QString::null; +} + +QString MergeObjectConstructor::selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const +{ + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + { + std::vector<ObjectCalcer*> args( sel ); + int wa = (*i)->wantArgs( args, d, w ); + if ( wa != ArgsParser::Invalid ) return (*i)->selectStatement( sel, d, w ); + }; + return QString::null; +} + +MacroConstructor::MacroConstructor( const ObjectHierarchy& hier, const QString& name, + const QString& desc, const QCString& iconfile ) + : ObjectConstructor(), mhier( hier ), mname( name ), mdesc( desc ), + mbuiltin( false ), miconfile( iconfile ), + mparser( mhier.argParser() ) +{ +} + +MacroConstructor::MacroConstructor( + const std::vector<ObjectCalcer*>& input, const std::vector<ObjectCalcer*>& output, + const QString& name, const QString& description, + const QCString& iconfile ) + : ObjectConstructor(), mhier( input, output ), + mname( name ), mdesc( description ), mbuiltin( false ), + miconfile( iconfile ), + mparser( mhier.argParser() ) +{ +} + +MacroConstructor::~MacroConstructor() +{ +} + +const QString MacroConstructor::descriptiveName() const +{ + return mname; +} + +const QString MacroConstructor::description() const +{ + return mdesc; +} + +const QCString MacroConstructor::iconFileName( const bool canBeNull ) const +{ + return ( miconfile.isNull() && !canBeNull ) ? QCString( "gear" ) : miconfile; +} + +const bool MacroConstructor::isAlreadySelectedOK( const std::vector<ObjectCalcer*>&, const int& ) const +{ + return false; +} + +const int MacroConstructor::wantArgs( const std::vector<ObjectCalcer*>& os, const KigDocument&, + const KigWidget& ) const +{ + return mparser.check( os ); +} + +void MacroConstructor::handleArgs( const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& ) const +{ + std::vector<ObjectCalcer*> args = mparser.parse( os ); + std::vector<ObjectCalcer*> bos = mhier.buildObjects( args, d.document() ); + std::vector<ObjectHolder*> hos; + for ( std::vector<ObjectCalcer*>::iterator i = bos.begin(); + i != bos.end(); ++i ) + { + hos.push_back( new ObjectHolder( *i ) ); + hos.back()->calc( d.document() ); + } + + d.addObjects( hos ); +} + +QString MacroConstructor::selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument&, + const KigWidget& ) const +{ + using namespace std; + Args args; + transform( sel.begin(), sel.end(), back_inserter( args ), + mem_fun( &ObjectCalcer::imp ) ); + std::string ret = mparser.selectStatement( args ); + if ( ret.empty() ) return QString::null; + else return i18n( ret.c_str() ); +} + +QString MacroConstructor::useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument&, const KigWidget& + ) const +{ + using namespace std; + Args args; + transform( sel.begin(), sel.end(), back_inserter( args ), + mem_fun( &ObjectCalcer::imp ) ); + std::string ret = mparser.usetext( o.imp(), args ); + if ( ret.empty() ) return QString::null; + else return i18n( ret.c_str() ); +} + +void MacroConstructor::handlePrelim( KigPainter& p, const std::vector<ObjectCalcer*>& sel, + const KigDocument& doc, const KigWidget& + ) const +{ + if ( sel.size() != mhier.numberOfArgs() ) return; + + using namespace std; + Args args; + transform( sel.begin(), sel.end(), back_inserter( args ), + mem_fun( &ObjectCalcer::imp ) ); + args = mparser.parse( args ); + std::vector<ObjectImp*> ret = mhier.calc( args, doc ); + for ( uint i = 0; i < ret.size(); ++i ) + { + ObjectDrawer d; + d.draw( *ret[i], p, true ); + ret[i]->draw( p ); + delete ret[i]; + }; +} + +void SimpleObjectTypeConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +void MultiObjectTypeConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +void MergeObjectConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +void MacroConstructor::plug( KigPart* doc, KigGUIAction* kact ) +{ + if ( mbuiltin ) return; + if ( mhier.numberOfResults() != 1 ) + doc->aMNewOther.append( kact ); + else + { + if ( mhier.idOfLastResult() == SegmentImp::stype() ) + doc->aMNewSegment.append( kact ); + else if ( mhier.idOfLastResult() == PointImp::stype() ) + doc->aMNewPoint.append( kact ); + else if ( mhier.idOfLastResult() == CircleImp::stype() ) + doc->aMNewCircle.append( kact ); + else if ( mhier.idOfLastResult()->inherits( AbstractLineImp::stype() ) ) + // line or ray + doc->aMNewLine.append( kact ); + else if ( mhier.idOfLastResult() == ConicImp::stype() ) + doc->aMNewConic.append( kact ); + else doc->aMNewOther.append( kact ); + }; + doc->aMNewAll.append( kact ); +} + +const ObjectHierarchy& MacroConstructor::hierarchy() const +{ + return mhier; +} + +bool SimpleObjectTypeConstructor::isTransform() const +{ + return mtype->isTransform(); +} + +bool MultiObjectTypeConstructor::isTransform() const +{ + return mtype->isTransform(); +} + +bool MergeObjectConstructor::isTransform() const +{ + bool ret = false; + for ( vectype::const_iterator i = mctors.begin(); i != mctors.end(); ++i ) + ret |= (*i)->isTransform(); + return ret; +} + +bool MacroConstructor::isTransform() const +{ + return false; +} + +void MacroConstructor::setBuiltin( bool builtin ) +{ + mbuiltin = builtin; +} + +bool ObjectConstructor::isIntersection() const +{ + return false; +} + +PropertyObjectConstructor::PropertyObjectConstructor( + const ObjectImpType* imprequirement, const char* usetext, + const char* selectstat, const char* descname, const char* desc, + const char* iconfile, const char* propertyinternalname ) + : StandardConstructorBase( descname, desc, iconfile, mparser ), + mpropinternalname( propertyinternalname ) +{ + ArgsParser::spec argsspec[1]; + argsspec[0].type = imprequirement; + argsspec[0].usetext = usetext; + argsspec[0].selectstat = selectstat; + mparser.initialize( argsspec, 1 ); +} + +PropertyObjectConstructor::~PropertyObjectConstructor() +{ +} + +void PropertyObjectConstructor::drawprelim( + const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& d ) const +{ + int index = parents[0]->imp()->propertiesInternalNames().findIndex( mpropinternalname ); + assert ( index != -1 ); + ObjectImp* imp = parents[0]->imp()->property( index, d ); + drawer.draw( *imp, p, true ); + delete imp; +} + +std::vector<ObjectHolder*> PropertyObjectConstructor::build( + const std::vector<ObjectCalcer*>& parents, KigDocument&, + KigWidget& ) const +{ + int index = parents[0]->imp()->propertiesInternalNames().findIndex( mpropinternalname ); + assert( index != -1 ); + std::vector<ObjectHolder*> ret; + ret.push_back( + new ObjectHolder( + new ObjectPropertyCalcer( parents[0], index ) ) ); + return ret; +} + +void PropertyObjectConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool PropertyObjectConstructor::isTransform() const +{ + return false; +} + +bool ObjectConstructor::isTest() const +{ + return false; +} + +BaseConstructMode* ObjectConstructor::constructMode( KigPart& doc ) +{ + return new ConstructMode( doc, this ); +} + +void MacroConstructor::setName( const QString& name ) +{ + mname = name; +} + +void MacroConstructor::setDescription( const QString& desc ) +{ + mdesc = desc; +} + +void MacroConstructor::setIcon( QCString& icon ) +{ + miconfile = icon; +} diff --git a/kig/misc/object_constructor.h b/kig/misc/object_constructor.h new file mode 100644 index 00000000..57261c69 --- /dev/null +++ b/kig/misc/object_constructor.h @@ -0,0 +1,396 @@ +// Copyright (C) 2002-2003 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. + +#ifndef KIG_MISC_OBJECT_CONSTRUCTOR_H +#define KIG_MISC_OBJECT_CONSTRUCTOR_H + +#include "argsparser.h" +#include "object_hierarchy.h" + +class KigPainter; +class KigDocument; +class KigGUIAction; +class KigWidget; +class ArgsParserObjectType; +class ObjectType; +class BaseConstructMode; + +class QString; +class QCString; + +/** + * This class represents a way to construct a set of objects from a + * set of other objects. There are some important child classes, like + * MacroConstructor, StandardObjectConstructor etc. ( see below ) + * Actually, it is more generic than that, it provides a way to do + * _something_ with a set of objects, but for now, i only use it to + * construct objects. Maybe some day, i'll find something more + * interesting to do with it, who knows... ;) + */ +class ObjectConstructor +{ +public: + virtual ~ObjectConstructor(); + + virtual const QString descriptiveName() const = 0; + virtual const QString description() const = 0; + virtual const QCString iconFileName( const bool canBeNull = false ) const = 0; + + /** + * the following function is called in case of duplication of arguments + * and returns true if this is acceptable; this will return false for + * typical objects + */ + virtual const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const = 0; + /** + * can this constructor do something useful with \p os ? return + * ArgsParser::Complete, Valid or NotGood + */ + virtual const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, + const KigWidget& v + ) const = 0; + + /** + * do something fun with \p os .. This func is only called if wantArgs + * returned Complete.. handleArgs should <i>not</i> do any + * drawing.. after somebody calls this function, he should + * redrawScreen() himself.. + */ + virtual void handleArgs( const std::vector<ObjectCalcer*>& os, + KigPart& d, + KigWidget& v + ) const = 0; + + /** + * return a string describing what you would use \p o for if it were + * selected... \p o should be part of \p sel . + */ + virtual QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const = 0; + + /** + * return a string describing what argument you want next, if the + * given selection of objects were selected. + */ + virtual QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const = 0; + + /** + * show a preliminary version of what you would do when \ref handleArgs + * would be called.. E.g. if this constructor normally constructs a + * locus through some 5 points, then it will try to draw a locus + * through whatever number of points it gets.. + */ + virtual void handlePrelim( KigPainter& p, + const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, + const KigWidget& v + ) const = 0; + + virtual void plug( KigPart* doc, KigGUIAction* kact ) = 0; + + virtual bool isTransform() const = 0; + virtual bool isTest() const; + virtual bool isIntersection() const; + + /** + * Which construct mode should be used for this ObjectConstructor. + * In fact, this is not a pretty design. The Kig + * GUIAction-ObjectConstructor stuff should be reworked into a + * general GUIAction, which just models something which can be + * executed given a certain number of arguments. The code for + * drawPrelim and such should all be in the ConstructMode, and the + * new GUIAction should just start the correct KigMode with the + * correct arguments. + * + * This function is only overridden in TestConstructor. + */ + virtual BaseConstructMode* constructMode( KigPart& doc ); +}; + +/** + * This class provides wraps ObjectConstructor in a more simple + * interface for the most common object types.. + */ +class StandardConstructorBase + : public ObjectConstructor +{ + const char* mdescname; + const char* mdesc; + const char* miconfile; + const ArgsParser& margsparser; +public: + StandardConstructorBase( const char* descname, + const char* desc, + const char* iconfile, + const ArgsParser& parser ); + + virtual ~StandardConstructorBase(); + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + virtual const int wantArgs( + const std::vector<ObjectCalcer*>& os, const KigDocument& d, + const KigWidget& v + ) const; + + void handleArgs( const std::vector<ObjectCalcer*>& os, + KigPart& d, + KigWidget& v + ) const; + + void handlePrelim( KigPainter& p, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + + virtual void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const = 0; + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v ) const; + + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + + virtual std::vector<ObjectHolder*> build( + const std::vector<ObjectCalcer*>& os, + KigDocument& d, KigWidget& w + ) const = 0; +}; + +/** + * A standard implementation of StandardConstructorBase for simple + * types.. + */ +class SimpleObjectTypeConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; +public: + SimpleObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile ); + + ~SimpleObjectTypeConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, + KigDocument& d, + KigWidget& w ) const; + + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +/** + * A standard implementation of StandardConstructorBase for property + * objects... + */ +class PropertyObjectConstructor + : public StandardConstructorBase +{ + ArgsParser mparser; + const char* mpropinternalname; +public: + PropertyObjectConstructor( + const ObjectImpType* imprequirement, const char* usetext, + const char* selectstat, const char* descname, const char* desc, + const char* iconfile, const char* propertyinternalname ); + + ~PropertyObjectConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, + KigDocument& d, KigWidget& w ) const; + + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +/** + * This class is the equivalent of \ref SimpleObjectTypeConstructor + * for object types that are constructed in groups of more than one. + * For example, the intersection of a circle and line in general + * produces two points, in general. Internally, we differentiate + * betweem them by passing them a parameter of ( in this case ) 1 or + * -1. There are still other object types that work the same, and + * they all require this sort of parameter. + * E.g. CubicLineIntersectionType takes a parameter between 1 and 3. + * This class knows about that, and constructs the objects along this + * scheme.. + */ +class MultiObjectTypeConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; + std::vector<int> mparams; + ArgsParser mparser; +public: + MultiObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile, + const std::vector<int>& params ); + MultiObjectTypeConstructor( + const ArgsParserObjectType* t, const char* descname, + const char* desc, const char* iconfile, + int a, int b, int c = -999, int d = -999 ); + ~MultiObjectTypeConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + + std::vector<ObjectHolder*> build( + const std::vector<ObjectCalcer*>& os, + KigDocument& d, KigWidget& w ) const; + + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +/** + * This class is a collection of some other ObjectConstructors, that + * makes them appear to the user as a single ObjectConstructor. It is + * e.g. used for the "intersection" constructor. + */ +class MergeObjectConstructor + : public ObjectConstructor +{ + const char* mdescname; + const char* mdesc; + const char* miconfilename; + typedef std::vector<ObjectConstructor*> vectype; + vectype mctors; +public: + MergeObjectConstructor( const char* descname, const char* desc, + const char* iconfilename ); + ~MergeObjectConstructor(); + + void merge( ObjectConstructor* e ); + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, + const KigWidget& v + ) const; + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v ) const; + + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + + void handleArgs( const std::vector<ObjectCalcer*>& os, KigPart& d, KigWidget& v ) const; + + void handlePrelim( KigPainter& p, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v ) const; + + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +/** + * MacroConstructor is a class that represents Kig macro's: these are + * constructed by the user, and defined by a set of input and a set of + * output objects. The macro-constructor saves the way in which the + * output objects have been built from the input objects, and when + * given similar input objects, it will produce objects in the given + * way. The data is saved in a \ref ObjectHierarchy. + */ +class MacroConstructor + : public ObjectConstructor +{ + ObjectHierarchy mhier; + QString mname; + QString mdesc; + bool mbuiltin; + QCString miconfile; + ArgsParser mparser; +public: + MacroConstructor( const std::vector<ObjectCalcer*>& input, const std::vector<ObjectCalcer*>& output, + const QString& name, const QString& description, + const QCString& iconfile = 0 ); + MacroConstructor( const ObjectHierarchy& hier, const QString& name, + const QString& desc, + const QCString& iconfile = 0 ); + ~MacroConstructor(); + + const ObjectHierarchy& hierarchy() const; + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, const KigDocument& d, + const KigWidget& v ) const; + + void handleArgs( const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& v ) const; + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + + void handlePrelim( KigPainter& p, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + + void plug( KigPart* doc, KigGUIAction* kact ); + + void setBuiltin( bool builtin ); + + /** + * is this the ctor for a transformation type. We want to know this + * cause transform types are shown separately in an object's RMB + * menu.. + */ + bool isTransform() const; + + void setName( const QString& name ); + void setDescription( const QString& desc ); + void setIcon( QCString& icon ); +}; + +#endif diff --git a/kig/misc/object_hierarchy.cc b/kig/misc/object_hierarchy.cc new file mode 100644 index 00000000..9102051a --- /dev/null +++ b/kig/misc/object_hierarchy.cc @@ -0,0 +1,774 @@ +// Copyright (C) 2003 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 "object_hierarchy.h" + +#include "../objects/object_holder.h" +#include "../objects/other_type.h" +#include "../objects/object_imp.h" +#include "../objects/object_imp_factory.h" +#include "../objects/object_type_factory.h" +#include "../objects/bogus_imp.h" +#include "../objects/transform_types.h" +#include "../objects/object_type.h" + +#include <kglobal.h> +#include <qdom.h> + +class ObjectHierarchy::Node +{ +public: + enum { ID_PushStack, ID_ApplyType, ID_FetchProp }; + virtual int id() const = 0; + + virtual ~Node(); + virtual Node* copy() const = 0; + + virtual void apply( std::vector<const ObjectImp*>& stack, int loc, + const KigDocument& ) const = 0; + + virtual void apply( std::vector<ObjectCalcer*>& stack, int loc ) const = 0; + + // this function is used to check whether the final objects depend + // on the given objects. The dependsstack contains a set of + // booleans telling which parts of the hierarchy certainly depend on + // the given objects. In this function, the node should check + // whether any of its parents have true set, and if so, set its own + // value to true. + virtual void checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const = 0; + // this function is used to check whether the given objects are all + // used by one or more of the final objects. The usedstack contains + // a set of booleans telling which parts of the hierarchy are + // certainly ancestors of the final objects. In this function, the + // node should set all of its parents' booleans to true. + virtual void checkArgumentsUsed( std::vector<bool>& usedstack ) const = 0; +}; + +ObjectHierarchy::Node::~Node() +{ +} + +class PushStackNode + : public ObjectHierarchy::Node +{ + ObjectImp* mimp; +public: + PushStackNode( ObjectImp* imp ) : mimp( imp ) {} + ~PushStackNode(); + + const ObjectImp* imp() const { return mimp; } + + int id() const; + Node* copy() const; + void apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& ) const; + void apply( std::vector<ObjectCalcer*>& stack, int loc ) const; + + void checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const; + void checkArgumentsUsed( std::vector<bool>& usedstack ) const; +}; + +void PushStackNode::checkArgumentsUsed( std::vector<bool>& ) const +{ +} + +void PushStackNode::apply( std::vector<ObjectCalcer*>& stack, int loc ) const +{ + stack[loc] = new ObjectConstCalcer( mimp->copy() ); +} + +void PushStackNode::checkDependsOnGiven( std::vector<bool>&, int ) const { + // pushstacknode depends on nothing.. + return; +} + +int PushStackNode::id() const { return ID_PushStack; } + +PushStackNode::~PushStackNode() +{ + delete mimp; +} + +ObjectHierarchy::Node* PushStackNode::copy() const +{ + return new PushStackNode( mimp->copy() ); +} + +void PushStackNode::apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& ) const +{ + stack[loc] = mimp->copy(); +} + +class ApplyTypeNode + : public ObjectHierarchy::Node +{ + const ObjectType* mtype; + std::vector<int> mparents; +public: + ApplyTypeNode( const ObjectType* type, const std::vector<int>& parents ) + : mtype( type ), mparents( parents ) {} + ~ApplyTypeNode(); + Node* copy() const; + + const ObjectType* type() const { return mtype; } + const std::vector<int>& parents() const { return mparents; } + + int id() const; + void apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& ) const; + void apply( std::vector<ObjectCalcer*>& stack, int loc ) const; + + void checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const; + void checkArgumentsUsed( std::vector<bool>& usedstack ) const; +}; + +int ApplyTypeNode::id() const { return ID_ApplyType; } + +void ApplyTypeNode::checkArgumentsUsed( std::vector<bool>& usedstack ) const +{ + for ( uint i = 0; i < mparents.size(); ++i ) + { + usedstack[mparents[i]] = true; + } +} + +void ApplyTypeNode::checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const +{ + bool result = false; + for ( uint i = 0; i < mparents.size(); ++i ) + if ( dependsstack[mparents[i]] == true ) result = true; + dependsstack[loc] = result; +} + +ApplyTypeNode::~ApplyTypeNode() +{ +} + +ObjectHierarchy::Node* ApplyTypeNode::copy() const +{ + return new ApplyTypeNode( mtype, mparents ); +} + +void ApplyTypeNode::apply( std::vector<ObjectCalcer*>& stack, int loc ) const +{ + std::vector<ObjectCalcer*> parents; + for ( uint i = 0; i < mparents.size(); ++i ) + parents.push_back( stack[ mparents[i] ] ); + stack[loc] = new ObjectTypeCalcer( mtype, parents ); +} + +void ApplyTypeNode::apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& doc ) const +{ + Args args; + for ( uint i = 0; i < mparents.size(); ++i ) + args.push_back( stack[mparents[i]] ); + args = mtype->sortArgs( args ); + stack[loc] = mtype->calc( args, doc ); +} + +class FetchPropertyNode + : public ObjectHierarchy::Node +{ + mutable int mpropid; + int mparent; + const QCString mname; +public: + // propid is a cache of the location of name in the parent's + // propertiesInternalNames(), just as it is in PropertyObject. We + // don't want to ever save this value, since we cannot guarantee it + // remains consistent if we add properties some place.. + FetchPropertyNode( const int parent, const QCString& name, const int propid = -1 ) + : mpropid( propid ), mparent( parent ), mname( name ) {} + ~FetchPropertyNode(); + Node* copy() const; + + void checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const; + void checkArgumentsUsed( std::vector<bool>& usedstack ) const; + int parent() const { return mparent; } + const QCString& propinternalname() const { return mname; } + + int id() const; + void apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& ) const; + void apply( std::vector<ObjectCalcer*>& stack, int loc ) const; +}; + +FetchPropertyNode::~FetchPropertyNode() +{ +} + +void FetchPropertyNode::checkArgumentsUsed( std::vector<bool>& usedstack ) const +{ + usedstack[mparent] = true; +} + +void FetchPropertyNode::checkDependsOnGiven( std::vector<bool>& dependsstack, int loc ) const +{ + dependsstack[loc] = dependsstack[mparent]; +} + +ObjectHierarchy::Node* FetchPropertyNode::copy() const +{ + return new FetchPropertyNode( mparent, mname, mpropid ); +} + +int FetchPropertyNode::id() const +{ + return ID_FetchProp; +} + +void FetchPropertyNode::apply( std::vector<const ObjectImp*>& stack, + int loc, const KigDocument& d ) const +{ + assert( stack[mparent] ); + if ( mpropid == -1 ) mpropid = stack[mparent]->propertiesInternalNames().findIndex( mname ); + if ( mpropid != -1 ) + stack[loc] = stack[mparent]->property( mpropid, d ); + else + stack[loc] = new InvalidImp(); +} + +void FetchPropertyNode::apply( std::vector<ObjectCalcer*>& stack, int loc ) const +{ + if ( mpropid == -1 ) + mpropid = stack[mparent]->imp()->propertiesInternalNames().findIndex( mname ); + assert( mpropid != -1 ); + stack[loc] = new ObjectPropertyCalcer( stack[mparent], mpropid ); +} + +std::vector<ObjectImp*> ObjectHierarchy::calc( const Args& a, const KigDocument& doc ) const +{ + assert( a.size() == mnumberofargs ); + for ( uint i = 0; i < a.size(); ++i ) + assert( a[i]->inherits( margrequirements[i] ) ); + + std::vector<const ObjectImp*> stack; + stack.resize( mnodes.size() + mnumberofargs, 0 ); + std::copy( a.begin(), a.end(), stack.begin() ); + for( uint i = 0; i < mnodes.size(); ++i ) + { + mnodes[i]->apply( stack, mnumberofargs + i, doc ); + }; + for ( uint i = mnumberofargs; i < stack.size() - mnumberofresults; ++i ) + delete stack[i]; + if ( stack.size() < mnumberofargs + mnumberofresults ) + { + std::vector<ObjectImp*> ret; + ret.push_back( new InvalidImp ); + return ret; + } + else + { + std::vector<ObjectImp*> ret; + for ( uint i = stack.size() - mnumberofresults; i < stack.size(); ++i ) + ret.push_back( const_cast<ObjectImp*>( stack[i] ) ); + return ret; + }; +} + +int ObjectHierarchy::visit( const ObjectCalcer* o, std::map<const ObjectCalcer*, int>& seenmap, + bool needed, bool neededatend ) +{ + using namespace std; + + std::map<const ObjectCalcer*, int>::iterator smi = seenmap.find( o ); + if ( smi != seenmap.end() ) + { + if ( neededatend ) + { + // neededatend means that this object is one of the resultant + // objects. Therefore, its node has to appear at the end, + // because that's where we expect it.. We therefore copy it + // there using CopyObjectType.. + int ret = mnumberofargs + mnodes.size(); + std::vector<int> parents; + parents.push_back( smi->second ); + mnodes.push_back( new ApplyTypeNode( CopyObjectType::instance(), parents ) ); + return ret; + } + else return smi->second; + } + + std::vector<ObjectCalcer*> p( o->parents() ); + // we check if o descends from the given objects.. + bool descendsfromgiven = false; + std::vector<int> parents; + parents.resize( p.size(), -1 ); + for ( uint i = 0; i < p.size(); ++i ) + { + int v = visit( p[i], seenmap, false ); + parents[i] = v; + descendsfromgiven |= (v != -1); + }; + + if ( ! descendsfromgiven && ! ( needed && o->imp()->isCache() ) ) + { + if ( needed ) + { + assert( ! o->imp()->isCache() ); + // o is an object that does not depend on the given objects, but + // is needed by other objects, so we just have to just save its + // current value here. + Node* node = new PushStackNode( o->imp()->copy() ); + mnodes.push_back( node ); + int ret = mnodes.size() + mnumberofargs - 1; + seenmap[o] = ret; + return ret; + } + else + return -1; + }; + + return storeObject( o, p, parents, seenmap ); +} + +ObjectHierarchy::~ObjectHierarchy() +{ + for ( uint i = 0; i < mnodes.size(); ++i ) delete mnodes[i]; +} + +ObjectHierarchy::ObjectHierarchy( const ObjectHierarchy& h ) + : mnumberofargs( h.mnumberofargs ), mnumberofresults( h.mnumberofresults ), + margrequirements( h.margrequirements ), musetexts( h.musetexts ), + mselectstatements( h.mselectstatements ) +{ + mnodes.reserve( h.mnodes.size() ); + for ( uint i = 0; i < h.mnodes.size(); ++i ) + mnodes.push_back( h.mnodes[i]->copy() ); +} + +ObjectHierarchy ObjectHierarchy::withFixedArgs( const Args& a ) const +{ + assert( a.size() <= mnumberofargs ); + ObjectHierarchy ret( *this ); + + ret.mnumberofargs -= a.size(); + ret.margrequirements.resize( ret.mnumberofargs ); + + std::vector<Node*> newnodes( mnodes.size() + a.size() ); + std::vector<Node*>::iterator newnodesiter = newnodes.begin(); + for ( uint i = 0; i < a.size(); ++i ) + { + assert( ! a[i]->isCache() ); + *newnodesiter++ = new PushStackNode( a[i]->copy() ); + }; + std::copy( ret.mnodes.begin(), ret.mnodes.end(), newnodesiter ); + ret.mnodes = newnodes; + + return ret; +} + +void ObjectHierarchy::init( const std::vector<ObjectCalcer*>& from, const std::vector<ObjectCalcer*>& to ) +{ + mnumberofargs = from.size(); + mnumberofresults = to.size(); + margrequirements.resize( from.size(), ObjectImp::stype() ); + musetexts.resize( margrequirements.size(), "" ); + std::map<const ObjectCalcer*, int> seenmap; + for ( uint i = 0; i < from.size(); ++i ) + seenmap[from[i]] = i; + for ( std::vector<ObjectCalcer*>::const_iterator i = to.begin(); i != to.end(); ++i ) + { + std::vector<ObjectCalcer*> parents = (*i)->parents(); + for ( std::vector<ObjectCalcer*>::const_iterator j = parents.begin(); + j != parents.end(); ++j ) + visit( *j, seenmap, true ); + } + for ( std::vector<ObjectCalcer*>::const_iterator i = to.begin(); i != to.end(); ++i ) + visit( *i, seenmap, true, true ); + + mselectstatements.resize( margrequirements.size(), "" ); +} + +ObjectHierarchy::ObjectHierarchy( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ) +{ + std::vector<ObjectCalcer*> tov; + tov.push_back( const_cast<ObjectCalcer*>( to ) ); + init( from, tov ); +} + +ObjectHierarchy::ObjectHierarchy( const std::vector<ObjectCalcer*>& from, const std::vector<ObjectCalcer*>& to ) +{ + init( from, to ); +} + +void ObjectHierarchy::serialize( QDomElement& parent, QDomDocument& doc ) const +{ + int id = 1; + for ( uint i = 0; i < mnumberofargs; ++i ) + { + QDomElement e = doc.createElement( "input" ); + e.setAttribute( "id", id++ ); + e.setAttribute( "requirement", margrequirements[i]->internalName() ); + // we don't save these atm, since the user can't define them. + // we only load them from builtin macro's. +// QDomElement ut = doc.createElement( "UseText" ); +// ut.appendChild( doc.createTextNode( QString::fromLatin1(musetexts[i].c_str() ) ) ); +// e.appendChild( ut ); +// QDomElement ss = doc.createElement( "SelectStatement" ); +// ss.appendChild( doc.createTextNode( QString::fromLatin1(mselectstatements[i].c_str() ) ) ); +// e.appendChild( ss ); + parent.appendChild( e ); + } + + for ( uint i = 0; i < mnodes.size(); ++i ) + { + bool result = mnodes.size() - ( id - mnumberofargs - 1 ) <= mnumberofresults; + QDomElement e = doc.createElement( result ? "result" : "intermediate" ); + e.setAttribute( "id", id++ ); + + if ( mnodes[i]->id() == Node::ID_ApplyType ) + { + const ApplyTypeNode* node = static_cast<const ApplyTypeNode*>( mnodes[i] ); + e.setAttribute( "action", "calc" ); + e.setAttribute( "type", QString::fromLatin1( node->type()->fullName() ) ); + for ( uint i = 0; i < node->parents().size(); ++i ) + { + int parent = node->parents()[i] + 1; + QDomElement arge = doc.createElement( "arg" ); + arge.appendChild( doc.createTextNode( QString::number( parent ) ) ); + e.appendChild( arge ); + }; + } + else if ( mnodes[i]->id() == Node::ID_FetchProp ) + { + const FetchPropertyNode* node = static_cast<const FetchPropertyNode*>( mnodes[i] ); + e.setAttribute( "action", "fetch-property" ); + e.setAttribute( "property", node->propinternalname() ); + QDomElement arge = doc.createElement( "arg" ); + arge.appendChild( doc.createTextNode( QString::number( node->parent() + 1 ) ) ); + e.appendChild( arge ); + } + else + { + assert( mnodes[i]->id() == ObjectHierarchy::Node::ID_PushStack ); + const PushStackNode* node = static_cast<const PushStackNode*>( mnodes[i] ); + e.setAttribute( "action", "push" ); + QString type = ObjectImpFactory::instance()->serialize( *node->imp(), e, doc ); + e.setAttribute( "type", type ); + }; + + parent.appendChild( e ); + }; +} + +ObjectHierarchy::ObjectHierarchy() + : mnumberofargs( 0 ), mnumberofresults( 0 ) +{ +} + +ObjectHierarchy* ObjectHierarchy::buildSafeObjectHierarchy( const QDomElement& parent, QString& error ) +{ +#define KIG_GENERIC_PARSE_ERROR \ + { \ + error = i18n( "An error was encountered at line %1 in file %2." ) \ + .arg( __LINE__ ).arg( __FILE__ ); \ + return 0; \ + } + + ObjectHierarchy* obhi = new ObjectHierarchy(); + + bool ok = true; + QString tmp; + QDomElement e = parent.firstChild().toElement(); + for (; !e.isNull(); e = e.nextSibling().toElement() ) + { + if ( e.tagName() != "input" ) break; + + tmp = e.attribute( "id" ); + uint id = tmp.toInt( &ok ); + if ( !ok ) KIG_GENERIC_PARSE_ERROR; + + obhi->mnumberofargs = kMax( id, obhi->mnumberofargs ); + + tmp = e.attribute( "requirement" ); + const ObjectImpType* req = ObjectImpType::typeFromInternalName( tmp.latin1() ); + if ( req == 0 ) req = ObjectImp::stype(); // sucks, i know.. + obhi->margrequirements.resize( obhi->mnumberofargs, ObjectImp::stype() ); + obhi->musetexts.resize( obhi->mnumberofargs, "" ); + obhi->mselectstatements.resize( obhi->mnumberofargs, "" ); + obhi->margrequirements[id - 1] = req; + obhi->musetexts[id - 1] = req->selectStatement(); + QDomElement esub = e.firstChild().toElement(); + for ( ; !esub.isNull(); esub = esub.nextSibling().toElement() ) + { + if ( esub.tagName() == "UseText" ) + { + obhi->musetexts[id - 1] = esub.text().latin1(); + } + else if ( esub.tagName() == "SelectStatement" ) + { + obhi->mselectstatements[id - 1] = esub.text().latin1(); + } + else + { + // broken file ? ignore... + } + } + } + for (; !e.isNull(); e = e.nextSibling().toElement() ) + { + bool result = e.tagName() == "result"; + if ( result ) ++obhi->mnumberofresults; + + tmp = e.attribute( "id" ); + int id = tmp.toInt( &ok ); + if ( !ok ) KIG_GENERIC_PARSE_ERROR; + + tmp = e.attribute( "action" ); + Node* newnode = 0; + if ( tmp == "calc" ) + { + // ApplyTypeNode + QCString typen = e.attribute( "type" ).latin1(); + const ObjectType* type = ObjectTypeFactory::instance()->find( typen ); + if ( ! type ) + { + error = i18n( "This Kig file uses an object of type \"%1\", " + "which this Kig version does not support." + "Perhaps you have compiled Kig without support " + "for this object type," + "or perhaps you are using an older Kig version." ).arg( typen ); + return 0; + } + + std::vector<int> parents; + for ( QDomNode p = e.firstChild(); !p.isNull(); p = p.nextSibling() ) + { + QDomElement q = p.toElement(); + if ( q.isNull() ) KIG_GENERIC_PARSE_ERROR; // see above + if ( q.tagName() != "arg" ) KIG_GENERIC_PARSE_ERROR; + int pid = q.text().toInt(&ok ); + if ( !ok ) KIG_GENERIC_PARSE_ERROR; + parents.push_back( pid - 1 ); + }; + newnode = new ApplyTypeNode( type, parents ); + } + else if ( tmp == "fetch-property" ) + { + // FetchPropertyNode + QCString propname = e.attribute( "property" ).latin1(); + QDomElement arge = e.firstChild().toElement(); + int parent = arge.text().toInt( &ok ); + if ( !ok ) KIG_GENERIC_PARSE_ERROR; + newnode = new FetchPropertyNode( parent - 1, propname ); + } + else + { + // PushStackNode + if ( e.attribute( "action" ) != "push" ) KIG_GENERIC_PARSE_ERROR; + QString typen = e.attribute( "type" ); + if ( typen.isNull() ) KIG_GENERIC_PARSE_ERROR; + ObjectImp* imp = ObjectImpFactory::instance()->deserialize( typen, e, error ); + if ( ( ! imp ) && !error.isEmpty() ) return 0; + newnode = new PushStackNode( imp ); + }; + obhi->mnodes.resize( kMax( size_t(id - obhi->mnumberofargs), obhi->mnodes.size() ) ); + obhi->mnodes[id - obhi->mnumberofargs - 1] = newnode; + }; + + // if we are here, all went fine + return obhi; +} + +ArgsParser ObjectHierarchy::argParser() const +{ + std::vector<ArgsParser::spec> specs; + for ( uint i = 0; i < margrequirements.size(); ++i ) + { + const ObjectImpType* req = margrequirements[i]; + ArgsParser::spec spec; + spec.type = req; + spec.usetext = musetexts[i]; + spec.selectstat = mselectstatements[i]; + specs.push_back( spec ); + }; + return ArgsParser( specs ); +} + +std::vector<ObjectCalcer*> ObjectHierarchy::buildObjects( const std::vector<ObjectCalcer*>& os, const KigDocument& doc ) const +{ + assert( os.size() == mnumberofargs ); + for ( uint i = 0; i < os.size(); ++i ) + assert( os[i]->imp()->inherits( margrequirements[i] ) ); + + std::vector<ObjectCalcer*> stack; + stack.resize( mnodes.size() + mnumberofargs, 0 ); + std::copy( os.begin(), os.end(), stack.begin() ); + + for( uint i = 0; i < mnodes.size(); ++i ) + { + mnodes[i]->apply( stack, mnumberofargs + i ); + stack[mnumberofargs + i]->calc( doc ); + }; + + std::vector<ObjectCalcer*> ret( stack.end() - mnumberofresults, stack.end() ); + + return ret; +} + +const ObjectImpType* ObjectHierarchy::idOfLastResult() const +{ + const Node* n = mnodes.back(); + if ( n->id() == Node::ID_PushStack ) + return static_cast<const PushStackNode*>( n )->imp()->type(); + else if ( n->id() == Node::ID_FetchProp ) + return ObjectImp::stype(); + else + return static_cast<const ApplyTypeNode*>( n )->type()->resultId(); +} + +ObjectHierarchy ObjectHierarchy::transformFinalObject( const Transformation& t ) const +{ + assert( mnumberofresults == 1 ); + ObjectHierarchy ret( *this ); + ret.mnodes.push_back( new PushStackNode( new TransformationImp( t ) ) ); + + std::vector<int> parents; + parents.push_back( ret.mnodes.size() - 1); + parents.push_back( ret.mnodes.size() ); + const ObjectType* type = ApplyTransformationObjectType::instance(); + ret.mnodes.push_back( new ApplyTypeNode( type, parents ) ); + return ret; +} + +bool operator==( const ObjectHierarchy& lhs, const ObjectHierarchy& rhs ) +{ + if ( ! ( lhs.mnumberofargs == rhs.mnumberofargs && + lhs.mnumberofresults == rhs.mnumberofresults && + lhs.margrequirements == rhs.margrequirements && + lhs.mnodes.size() == rhs.mnodes.size() ) ) + return false; + + // this isn't entirely correct, but it will do, because we don't + // really want to know whether the hierarchies are different, but + // whether rhs has changed with regard to lhs.. + for ( uint i = 0; i < lhs.mnodes.size(); ++i ) + if ( lhs.mnodes[i] != lhs.mnodes[i] ) + return false; + + return true; +} + +bool ObjectHierarchy::resultDoesNotDependOnGiven() const +{ + std::vector<bool> dependsstack( mnodes.size() + mnumberofargs, false ); + + for ( uint i = 0; i < mnumberofargs; ++i ) + dependsstack[i] = true; + for ( uint i = 0; i < mnodes.size(); ++i ) + mnodes[i]->checkDependsOnGiven( dependsstack, i + mnumberofargs ); + for ( uint i = dependsstack.size() - mnumberofresults; i < dependsstack.size(); ++i ) + if ( !dependsstack[i] ) + return true; + return false; +} + +// returns the "minimum" of a and b ( in the partially ordered set of +// ObjectImpType's, using the inherits member function as comparison, +// if you for some reason like this sort of non-sense ;) ). This +// basically means: return the type that inherits the other type, +// because if another type inherits the lowermost type, then it will +// also inherit the other.. +const ObjectImpType* lowermost( const ObjectImpType* a, const ObjectImpType* b ) +{ + if ( a->inherits( b ) ) return a; + assert( b->inherits( a ) ); + return b; +} + +// this function is part of the visit procedure really. It is +// factored out, because it recurses for cache ObjectImp's. What this +// does is, it makes sure that object o is calcable, by putting +// appropriate Node's in mnodes.. po is o->parents() and pl contains +// the location of objects that are already in mnodes and -1 +// otherwise.. -1 means we have to store their ObjectImp, unless +// they're cache ObjectImp's etc. +int ObjectHierarchy::storeObject( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& po, std::vector<int>& pl, + std::map<const ObjectCalcer*, int>& seenmap ) +{ + for ( uint i = 0; i < po.size(); ++i ) + { + if ( pl[i] == -1 ) + { + // we can't store cache ObjectImp's.. + if ( po[i]->imp()->isCache() ) + { + pl[i] = visit( po[i], seenmap, true, false ); + } + else + { + Node* argnode = new PushStackNode( po[i]->imp()->copy() ); + mnodes.push_back( argnode ); + int argloc = mnumberofargs + mnodes.size() - 1; + seenmap[po[i]] = argloc; + pl[i] = argloc; + }; + } + else if ( (uint) pl[i] < mnumberofargs ) + { + ObjectCalcer* parent = o->parents()[i]; + std::vector<ObjectCalcer*> opl = o->parents(); + + margrequirements[pl[i]] = + lowermost( margrequirements[pl[i]], + o->impRequirement( parent, opl ) ); + musetexts[pl[i]] = margrequirements[pl[i]]->selectStatement(); + }; + }; + if ( dynamic_cast<const ObjectTypeCalcer*>( o ) ) + mnodes.push_back( new ApplyTypeNode( static_cast<const ObjectTypeCalcer*>( o )->type(), pl ) ); + else if ( dynamic_cast<const ObjectPropertyCalcer*>( o ) ) + { + assert( pl.size() == 1 ); + int parent = pl.front(); + ObjectCalcer* op = po.front(); + assert( op ); + uint propid = static_cast<const ObjectPropertyCalcer*>( o )->propId(); + assert( propid < op->imp()->propertiesInternalNames().size() ); + mnodes.push_back( new FetchPropertyNode( parent, op->imp()->propertiesInternalNames()[propid], propid ) ); + } + else + assert( false ); + seenmap[o] = mnumberofargs + mnodes.size() - 1; + return mnumberofargs + mnodes.size() - 1; +} + +ObjectHierarchy::ObjectHierarchy( const ObjectCalcer* from, const ObjectCalcer* to ) +{ + std::vector<ObjectCalcer*> fromv; + fromv.push_back( const_cast<ObjectCalcer*>( from ) ); + std::vector<ObjectCalcer*> tov; + tov.push_back( const_cast<ObjectCalcer*>( to ) ); + init( fromv, tov ); +} + +bool ObjectHierarchy::allGivenObjectsUsed() const +{ + std::vector<bool> usedstack( mnodes.size() + mnumberofargs, false ); + for ( uint i = mnodes.size() - mnumberofresults; i < mnodes.size(); ++i ) + usedstack[i + mnumberofargs] = true; + for ( int i = mnodes.size() - 1; i >= 0; --i ) + if ( usedstack[i + mnumberofargs] ) + mnodes[i]->checkArgumentsUsed( usedstack ); + for ( uint i = 0; i < mnumberofargs; ++i ) + if ( ! usedstack[i] ) return false; + return true; +} + diff --git a/kig/misc/object_hierarchy.h b/kig/misc/object_hierarchy.h new file mode 100644 index 00000000..3133dc7c --- /dev/null +++ b/kig/misc/object_hierarchy.h @@ -0,0 +1,111 @@ +// Copyright (C) 2003 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. + +#ifndef KIG_MISC_OBJECT_HIERARCHY_H +#define KIG_MISC_OBJECT_HIERARCHY_H + +#include "../objects/common.h" + +#include <map> +#include <vector> +#include <string> + +class ObjectImpType; +class ArgsParser; + +class ObjectHierarchy +{ +public: + class Node; +private: + std::vector<Node*> mnodes; + uint mnumberofargs; + uint mnumberofresults; + std::vector<const ObjectImpType*> margrequirements; + std::vector<std::string> musetexts; + std::vector<std::string> mselectstatements; + + // these two are really part of the constructor... + int visit( const ObjectCalcer* o, std::map<const ObjectCalcer*, int>&, + bool needed, bool neededatend = false); + int storeObject( const ObjectCalcer*, const std::vector<ObjectCalcer*>& po, std::vector<int>& pl, + std::map<const ObjectCalcer*, int>& seenmap ); + + friend bool operator==( const ObjectHierarchy& lhs, const ObjectHierarchy& rhs ); + + void init( const std::vector<ObjectCalcer*>& from, const std::vector<ObjectCalcer*>& to ); + + /** + * this constructor is private since it should be used only by the static + * constructor buildSafeObjectHierarchy + * + * \see ObjectHierarchy::buildSafeObjectHierarchy + */ + ObjectHierarchy(); + +public: + ObjectHierarchy( const ObjectCalcer* from, const ObjectCalcer* to ); + ObjectHierarchy( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to ); + ObjectHierarchy( const std::vector<ObjectCalcer*>& from, const std::vector<ObjectCalcer*>& to ); + ObjectHierarchy( const ObjectHierarchy& h ); + ~ObjectHierarchy(); + + /** + * this creates a new ObjectHierarchy, that takes a.size() less + * arguments, but uses copies of the ObjectImp's in \p a instead.. + */ + ObjectHierarchy withFixedArgs( const Args& a ) const; + + std::vector<ObjectImp*> calc( const Args& a, const KigDocument& doc ) const; + + /** + * saves the ObjectHierarchy data in children xml tags of \p parent .. + */ + void serialize( QDomElement& parent, QDomDocument& doc ) const; + /** + * Deserialize the ObjectHierarchy data from the xml element \p parent .. + * Since this operation can fail for some reasons, we provide it as a + * static to return 0 in case of error. + */ + static ObjectHierarchy* buildSafeObjectHierarchy( const QDomElement& parent, QString& error ); +// ObjectHierarchy( const QDomElement& parent ); + + /** + * build a set of objects that interdepend according to this + * ObjectHierarchy.. Only the result objects are returned. Helper + * objects that connect the given objects with the returned objects, + * can only be found by following the returned objects' parents() + * methods.. + */ + std::vector<ObjectCalcer*> buildObjects( const std::vector<ObjectCalcer*>& os, const KigDocument& ) const; + + ArgsParser argParser() const; + + uint numberOfArgs() const { return mnumberofargs; } + uint numberOfResults() const { return mnumberofresults; } + + const ObjectImpType* idOfLastResult() const; + + bool resultDoesNotDependOnGiven() const; + bool allGivenObjectsUsed() const; + + ObjectHierarchy transformFinalObject( const Transformation& t ) const; +}; + +bool operator==( const ObjectHierarchy& lhs, const ObjectHierarchy& rhs ); + +#endif diff --git a/kig/misc/rect.cc b/kig/misc/rect.cc new file mode 100644 index 00000000..dc28de82 --- /dev/null +++ b/kig/misc/rect.cc @@ -0,0 +1,308 @@ +// 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 "rect.h" +#include "common.h" + +bool operator==( const Rect& r, const Rect& s ) +{ + return ( r.bottomLeft() == s.bottomLeft() + && r.width() == s.width() + && r.height() == s.height() ); +} + +kdbgstream& operator<<( kdbgstream& s, const Rect& t ) +{ + s << "left: " << t.left() + << "bottom: " << t.bottom() + << "right: " << t.right() + << "top: " << t.top() + << endl; + return s; +} + +Rect::Rect( const Coordinate bottomLeft, const Coordinate topRight ) + : mBottomLeft(bottomLeft) +{ + mwidth = topRight.x - bottomLeft.x; + mheight = topRight.y - bottomLeft.y; + normalize(); +} + +Rect::Rect( const Coordinate p, const double width, const double height ) + : mBottomLeft(p), + mwidth(width), + mheight(height) +{ + normalize(); +} + +Rect::Rect( double xa, double ya, double width, double height ) + : mBottomLeft( xa, ya ), + mwidth( width ), + mheight( height ) +{ + normalize(); +} + +Rect::Rect( const Rect& r ) + : mBottomLeft (r.mBottomLeft), + mwidth(r.mwidth), + mheight(r.mheight) +{ + normalize(); +} + +Rect::Rect() + : mwidth(0), + mheight(0) +{ +} + +void Rect::setBottomLeft( const Coordinate p ) +{ + mBottomLeft = p; +} + +void Rect::setBottomRight( const Coordinate p ) +{ + mBottomLeft = p - Coordinate(mwidth,0); +} + +void Rect::setTopRight( const Coordinate p ) +{ + mBottomLeft = p - Coordinate(mwidth, mheight); +} + +void Rect::setCenter( const Coordinate p ) +{ + mBottomLeft = p - Coordinate(mwidth, mheight)/2; +} + +void Rect::setLeft( const double p ) +{ + double r = right(); + mBottomLeft.x = p; + setRight( r ); +} + +void Rect::setRight( const double p ) +{ + mwidth = p - left(); +} + +void Rect::setBottom( const double p ) +{ + double t = top(); + mBottomLeft.y = p; + setTop( t ); +} + +void Rect::setTop( const double p ) +{ + mheight = p - bottom(); +} + +void Rect::setWidth( const double w ) +{ + mwidth = w; +} + +void Rect::setHeight( const double h ) +{ + mheight = h; +} + +void Rect::normalize() +{ + if ( mwidth < 0 ) + { + mBottomLeft.x += mwidth; + mwidth = -mwidth; + }; + if ( mheight < 0 ) + { + mBottomLeft.y += mheight; + mheight = -mheight; + }; +} + +void Rect::moveBy( const Coordinate p ) +{ + mBottomLeft += p; +} + +void Rect::scale( const double r ) +{ + mwidth *= r; + mheight *= r; +} + + +QRect Rect::toQRect() const +{ + return QRect(mBottomLeft.toQPoint(), topRight().toQPoint()); +} + +Coordinate Rect::bottomLeft() const +{ + return mBottomLeft; +} + +Coordinate Rect::bottomRight() const +{ + return mBottomLeft + Coordinate(mwidth, 0); +} + +Coordinate Rect::topLeft() const +{ + return mBottomLeft + Coordinate(0, mheight); +} + +Coordinate Rect::topRight() const +{ + return mBottomLeft + Coordinate(mwidth, mheight); +} + +Coordinate Rect::center() const +{ + return mBottomLeft + Coordinate(mwidth, mheight)/2; +} + +double Rect::left() const +{ + return mBottomLeft.x; +} +double Rect::right() const +{ + return left() + mwidth; +} +double Rect::bottom() const +{ + return mBottomLeft.y; +} + +double Rect::top() const +{ + return bottom() + mheight; +} + +double Rect::width() const +{ + return mwidth; +} + +double Rect::height() const +{ + return mheight; +} + +bool Rect::contains( const Coordinate& p, double allowed_miss ) const +{ + return p.x - left() >= - allowed_miss && + p.y - bottom() >= - allowed_miss && + p.x - left() - width() <= allowed_miss && + p.y - bottom() - height() <= allowed_miss; +} + +bool Rect::contains( const Coordinate& p ) const +{ + return p.x >= left() && + p.y >= bottom() && + p.x - left() <= width() && + p.y - bottom() <= height(); +} + +bool Rect::intersects( const Rect& p ) const +{ + // never thought it was this simple :) + if( p.left() < left() && p.right() < left()) return false; + if( p.left() > right() && p.right() > right()) return false; + if( p.bottom() < bottom() && p.top() < bottom()) return false; + if( p.bottom() > top() && p.top() > top()) return false; + return true; +} + +void Rect::setContains( Coordinate p ) +{ + normalize(); + if( p.x < left() ) setLeft( p.x ); + if( p.x > right() ) setRight(p.x); + if( p.y < bottom() ) setBottom( p.y ); + if( p.y > top() ) setTop( p.y ); +} + +Rect Rect::normalized() const +{ + Rect t = *this; + (void) t.normalize(); + return t; +} + +Rect Rect::fromQRect( const QRect& r ) +{ + return Rect( r.left(), r.top(), r.right(), r.bottom() ); +} + +void Rect::setTopLeft( const Coordinate p ) +{ + Coordinate bl = Coordinate( p.x, p.y - mheight ); + setBottomLeft( bl ); +} + +Rect operator|( const Rect& lhs, const Rect& rhs ) +{ + Rect r( lhs ); + r |= rhs; + return r; +} + +void Rect::eat( const Rect& r ) +{ + setLeft( kigMin( left(), r.left() ) ); + setRight( kigMax( right(), r.right() ) ); + setBottom( kigMin( bottom(), r.bottom() ) ); + setTop( kigMax( top(), r.top() ) ); +} + +Rect Rect::matchShape( const Rect& rhs, bool shrink ) const +{ + Rect ret = *this; + Coordinate c = center(); + double v = width()/height(); // current ratio + double w = rhs.width()/rhs.height(); // wanted ratio + + // we don't show less than r, if the dimensions don't match, we + // extend r into some dimension... + if( ( v > w ) ^ shrink ) + ret.setHeight( ret.width() / w ); + else + ret.setWidth( ret.height() * w ); + + ret.setCenter(c); + return ret.normalized(); +} + +bool Rect::valid() +{ + return mBottomLeft.valid() && mwidth != double_inf && mheight != double_inf; +} + +Rect Rect::invalidRect() +{ + return Rect( Coordinate::invalidCoord(), double_inf, double_inf ); +} diff --git a/kig/misc/rect.h b/kig/misc/rect.h new file mode 100644 index 00000000..a222d1ab --- /dev/null +++ b/kig/misc/rect.h @@ -0,0 +1,140 @@ +/** + 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 +**/ + + +#ifndef RECT_H +#define RECT_H + +#include "coordinate.h" + +#include <qrect.h> +#include <kdebug.h> + +/** + * like Coordinate is a QPoint replacement with doubles, this is a + * QRect replacement with doubles... + */ +class Rect +{ +public: + /** + * constructors... + */ + Rect( const Coordinate bottomLeft, const Coordinate topRight ); + Rect( const Coordinate bottomLeft, const double width, const double height ); + Rect( double xa, double ya, double width, double height ); + Rect( const Rect& r ); + Rect(); + static Rect invalidRect(); + + + bool valid(); + + void setBottomLeft( const Coordinate p ); + void setTopLeft( const Coordinate p ); + void setTopRight( const Coordinate p ); + void setBottomRight( const Coordinate p ); + void setCenter( const Coordinate p ); + void setLeft( const double p); + void setRight( const double p); + void setTop( const double p ); + void setBottom( const double p ); + void setWidth( const double w ); + void setHeight( const double h ); + /** + * this makes sure width and height are > 0 ... + */ + void normalize(); + /** + * this makes sure p is in the rect, extending it if necessary... + */ + void setContains( Coordinate p ); + /** + * moves the rect while keeping the size constant... + */ + void moveBy( const Coordinate p ); + /** + * synonym for moveBy... + */ + Rect& operator+=( const Coordinate p ) { moveBy(p); return *this; } + /** + * scale: only the size changes, topLeft is kept where it is... + */ + void scale( const double r ); + /** + * synonym for scale... + */ + Rect& operator*=( const double r ) { scale(r); return *this; } + Rect& operator/=( const double r ) { scale(1/r); return *this; } + + /** + * This expands the rect so that it contains r. It has friends + * '|=' and '|' below... + */ + void eat( const Rect& r ); + + /** + * synonym for eat.. + */ + Rect& operator|=( const Rect& rhs ) { eat( rhs ); return *this; } + + /** + * return a rect which is a copy of this rect, but has an aspect + * ratio equal to rhs's one.. if \p shrink is true, the rect will be + * shrunk, otherwise extended.. The center of the new rect is the + * same as this rect's center.. + */ + Rect matchShape( const Rect& rhs, bool shrink = false ) const; + + QRect toQRect() const; + Coordinate bottomLeft() const; + Coordinate bottomRight() const; + Coordinate topLeft() const; + Coordinate topRight() const; + Coordinate center() const; + double left() const; + double right() const; + double bottom() const; + double top() const; + double width() const; + double height() const; + bool contains( const Coordinate& p ) const; + bool contains( const Coordinate& p, double allowed_miss ) const; + bool intersects( const Rect& p ) const; + Rect normalized() const; + friend kdbgstream& operator<<( kdbgstream& s, const Rect& t ); + + static Rect fromQRect( const QRect& ); +protected: + Coordinate mBottomLeft; + double mwidth; + double mheight; +}; + +bool operator==( const Rect& r, const Rect& s ); +kdbgstream& operator<<( kdbgstream& s, const Rect& t ); +/** + * this operator returns a Rect that contains both the given + * rects.. + */ +Rect operator|( const Rect& lhs, const Rect& rhs ); + +#endif + diff --git a/kig/misc/screeninfo.cc b/kig/misc/screeninfo.cc new file mode 100644 index 00000000..c1418876 --- /dev/null +++ b/kig/misc/screeninfo.cc @@ -0,0 +1,92 @@ +// 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 "screeninfo.h" + +#include <cmath> + +ScreenInfo::ScreenInfo( const Rect& docRect, const QRect& viewRect ) + : mkrect( docRect.normalized() ), mqrect( viewRect.normalize() ) +{ +} + +Rect ScreenInfo::fromScreen( const QRect& r ) const +{ + return Rect( + fromScreen( r.topLeft() ), + fromScreen( r.bottomRight() ) + ).normalized(); +} + +Coordinate ScreenInfo::fromScreen( const QPoint& p ) const +{ + // invert the y-axis: 0 is at the bottom ! + Coordinate t( p.x(), mqrect.height() - p.y() ); + t *= mkrect.width(); + t /= mqrect.width(); + return t + mkrect.bottomLeft(); +} + +QPoint ScreenInfo::toScreen( const Coordinate& p ) const +{ + Coordinate t = p - mkrect.bottomLeft(); + t *= mqrect.width(); + t /= mkrect.width(); + // invert the y-axis: 0 is at the bottom ! + return QPoint( (int) t.x, mqrect.height() - (int) t.y ); +} + +QRect ScreenInfo::toScreen( const Rect& r ) const +{ + return QRect( + toScreen( r.bottomLeft() ), + toScreen( r.topRight() ) + ).normalize(); +} + +double ScreenInfo::pixelWidth() const +{ + Coordinate a = fromScreen( QPoint( 0, 0 ) ); + Coordinate b = fromScreen( QPoint( 0, 1000 ) ); + return std::fabs( b.y - a.y ) / 1000; +} + +const Rect& ScreenInfo::shownRect() const +{ + return mkrect; +} + +void ScreenInfo::setShownRect( const Rect& r ) +{ + mkrect = r; +} + +const QRect ScreenInfo::viewRect() const +{ + return mqrect; +} + +void ScreenInfo::setViewRect( const QRect& r ) +{ + mqrect = r; +} + +double ScreenInfo::normalMiss( int width ) const +{ + int twidth = width == -1 ? 1 : width; + return (twidth+2)*pixelWidth(); +} diff --git a/kig/misc/screeninfo.h b/kig/misc/screeninfo.h new file mode 100644 index 00000000..b7f94c49 --- /dev/null +++ b/kig/misc/screeninfo.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef SCREENINFO_H +#define SCREENINFO_H + +#include <qrect.h> + +#include "rect.h" + +/** + * ScreenInfo is a simple utility class that maps a region of the + * document onto a region of the screen. It is used by both + * KigPainter and KigWidget, and the objects use it in their calc() + * method... + */ +class ScreenInfo +{ + Rect mkrect; + QRect mqrect; +public: + ScreenInfo( const Rect& docRect, const QRect& viewRect ); + + Coordinate fromScreen( const QPoint& p ) const; + Rect fromScreen( const QRect& r ) const; + + QPoint toScreen( const Coordinate& p ) const; + QRect toScreen( const Rect& r ) const; + + double pixelWidth() const; + + double normalMiss( int width ) const; + + const Rect& shownRect() const; + + void setShownRect( const Rect& r ); + + const QRect viewRect() const; + + void setViewRect( const QRect& r ); +}; + +#endif diff --git a/kig/misc/special_constructors.cc b/kig/misc/special_constructors.cc new file mode 100644 index 00000000..04c8a097 --- /dev/null +++ b/kig/misc/special_constructors.cc @@ -0,0 +1,1628 @@ +// Copyright (C) 2003 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 "special_constructors.h" + +#include "calcpaths.h" +#include "common.h" +#include "conic-common.h" +#include "guiaction.h" +#include "kigpainter.h" + +#include "../kig/kig_part.h" +#include "../modes/construct_mode.h" +#include "../objects/bogus_imp.h" +#include "../objects/centerofcurvature_type.h" +#include "../objects/circle_imp.h" +#include "../objects/conic_imp.h" +#include "../objects/conic_types.h" +#include "../objects/cubic_imp.h" +#include "../objects/intersection_types.h" +#include "../objects/inversion_type.h" +#include "../objects/line_imp.h" +#include "../objects/line_type.h" +#include "../objects/locus_imp.h" +#include "../objects/object_calcer.h" +#include "../objects/object_drawer.h" +#include "../objects/object_factory.h" +#include "../objects/object_holder.h" +#include "../objects/object_imp.h" +#include "../objects/object_type.h" +#include "../objects/other_imp.h" +#include "../objects/other_type.h" +#include "../objects/point_imp.h" +#include "../objects/point_type.h" +#include "../objects/polygon_imp.h" +#include "../objects/polygon_type.h" +#include "../objects/tangent_type.h" +#include "../objects/text_imp.h" +#include "../objects/transform_types.h" + +#include <qpen.h> + +#include <klocale.h> + +#include <algorithm> +#include <functional> + +class ConicConicIntersectionConstructor + : public StandardConstructorBase +{ +protected: + ArgsParser mparser; +public: + ConicConicIntersectionConstructor(); + ~ConicConicIntersectionConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +class ConicLineIntersectionConstructor + : public MultiObjectTypeConstructor +{ +public: + ConicLineIntersectionConstructor(); + ~ConicLineIntersectionConstructor(); +}; + +class ArcLineIntersectionConstructor + : public MultiObjectTypeConstructor +{ +public: + ArcLineIntersectionConstructor(); + ~ArcLineIntersectionConstructor(); +}; + +ConicRadicalConstructor::ConicRadicalConstructor() + : StandardConstructorBase( + I18N_NOOP( "Radical Lines for Conics" ), + I18N_NOOP( "The lines constructed through the intersections " + "of two conics. This is also defined for " + "non-intersecting conics." ), + "conicsradicalline", mparser ), + mtype( ConicRadicalType::instance() ), + mparser( mtype->argsParser().without( IntImp::stype() ) ) +{ +} + +ConicRadicalConstructor::~ConicRadicalConstructor() +{ +} + +void ConicRadicalConstructor::drawprelim( + const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& doc ) const +{ + if ( parents.size() == 2 && parents[0]->imp()->inherits( ConicImp::stype() ) && + parents[1]->imp()->inherits( ConicImp::stype() ) ) + { + Args args; + std::transform( parents.begin(), parents.end(), + std::back_inserter( args ), std::mem_fun( &ObjectCalcer::imp ) ); + for ( int i = -1; i < 2; i += 2 ) + { + IntImp root( i ); + IntImp zeroindex( 1 ); + args.push_back( &root ); + args.push_back( &zeroindex ); + ObjectImp* data = mtype->calc( args, doc ); + drawer.draw( *data, p, true ); + delete data; data = 0; + args.pop_back(); + args.pop_back(); + }; + }; +} + +std::vector<ObjectHolder*> ConicRadicalConstructor::build( const std::vector<ObjectCalcer*>& os, KigDocument&, KigWidget& ) const +{ + using namespace std; + std::vector<ObjectHolder*> ret; + ObjectCalcer* zeroindexcalcer = new ObjectConstCalcer( new IntImp( 1 ) ); + for ( int i = -1; i < 2; i += 2 ) + { + std::vector<ObjectCalcer*> args; + std::copy( os.begin(), os.end(), back_inserter( args ) ); + args.push_back( new ObjectConstCalcer( new IntImp( i ) ) ); + // we use only one zeroindex dataobject, so that if you switch one + // radical line around, then the other switches along.. + args.push_back( zeroindexcalcer ); + ret.push_back( + new ObjectHolder( new ObjectTypeCalcer( mtype, args ) ) ); + }; + return ret; +} + +static const struct ArgsParser::spec argsspecpp[] = +{ + { PointImp::stype(), I18N_NOOP( "Moving Point" ), + I18N_NOOP( "Select the moving point, which will be moved around while drawing the locus..." ), false }, + { PointImp::stype(), I18N_NOOP( "Following Point" ), + I18N_NOOP( "Select the following point, whose locations the locus will be drawn through..." ), true } +}; + +LocusConstructor::LocusConstructor() + : StandardConstructorBase( I18N_NOOP( "Locus" ), I18N_NOOP( "A locus" ), + "locus", margsparser ), + margsparser( argsspecpp, 2 ) +{ +} + +LocusConstructor::~LocusConstructor() +{ +} + +void LocusConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + // this function is rather ugly, but it is necessary to do it this + // way in order to play nice with Kig's design.. + + if ( parents.size() != 2 ) return; + const ObjectTypeCalcer* constrained = dynamic_cast<ObjectTypeCalcer*>( parents.front() ); + const ObjectCalcer* moving = parents.back(); + if ( ! constrained || ! constrained->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) + { + // moving is in fact the constrained point.. swap them.. + moving = parents.front(); + constrained = dynamic_cast<const ObjectTypeCalcer*>( parents.back() ); + assert( constrained ); + }; + assert( constrained->type()->inherits( ObjectType::ID_ConstrainedPointType ) ); + + const ObjectImp* oimp = constrained->parents().back()->imp(); + if( !oimp->inherits( CurveImp::stype() ) ) + oimp = constrained->parents().front()->imp(); + assert( oimp->inherits( CurveImp::stype() ) ); + const CurveImp* cimp = static_cast<const CurveImp*>( oimp ); + + ObjectHierarchy hier( constrained, moving ); + + LocusImp limp( cimp->copy(), hier ); + drawer.draw( limp, p, true ); +} + +const int LocusConstructor::wantArgs( + const std::vector<ObjectCalcer*>& os, const KigDocument&, const KigWidget& + ) const +{ + int ret = margsparser.check( os ); + if ( ret == ArgsParser::Invalid ) return ret; + else if ( os.size() != 2 ) return ret; + if ( dynamic_cast<ObjectTypeCalcer*>( os.front() ) && + static_cast<ObjectTypeCalcer*>( os.front() )->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) + { + std::set<ObjectCalcer*> children = getAllChildren( os.front() ); + return children.find( os.back() ) != children.end() ? ret : ArgsParser::Invalid; + } + if ( dynamic_cast<ObjectTypeCalcer*>( os.back() ) && + static_cast<ObjectTypeCalcer*>( os.back() )->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) + { + std::set<ObjectCalcer*> children = getAllChildren( os.back() ); + return children.find( os.front() ) != children.end() ? ret : ArgsParser::Invalid; + } + return ArgsParser::Invalid; +} + +std::vector<ObjectHolder*> LocusConstructor::build( const std::vector<ObjectCalcer*>& parents, KigDocument&, KigWidget& ) const +{ + std::vector<ObjectHolder*> ret; + assert( parents.size() == 2 ); + + ObjectTypeCalcer* constrained = dynamic_cast<ObjectTypeCalcer*>( parents.front() ); + ObjectCalcer* moving = parents.back(); + if ( ! constrained || ! constrained->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) + { + // moving is in fact the constrained point.. swap them.. + moving = parents.front(); + constrained = dynamic_cast<ObjectTypeCalcer*>( parents.back() ); + assert( constrained ); + }; + assert( constrained->type()->inherits( ObjectType::ID_ConstrainedPointType ) ); + + ret.push_back( ObjectFactory::instance()->locus( constrained, moving ) ); + return ret; +} + +QString LocusConstructor::useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& os, + const KigDocument&, const KigWidget& ) const +{ + if ( dynamic_cast<const ObjectTypeCalcer*>( &o ) && + static_cast<const ObjectTypeCalcer&>( o ).type()->inherits( ObjectType::ID_ConstrainedPointType ) && + ( os.empty() || !dynamic_cast<ObjectTypeCalcer*>( os[0] ) || + !static_cast<const ObjectTypeCalcer*>( os[0] )->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) + ) return i18n( "Moving Point" ); + else return i18n( "Dependent Point" ); +} + +void ConicRadicalConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +void LocusConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool ConicRadicalConstructor::isTransform() const +{ + return mtype->isTransform(); +} + +bool LocusConstructor::isTransform() const +{ + return false; +} + +/* + * generic polygon constructor + */ + +PolygonBNPTypeConstructor::PolygonBNPTypeConstructor() + : mtype( PolygonBNPType::instance() ) +{ +} + +PolygonBNPTypeConstructor::~PolygonBNPTypeConstructor() +{ +} + +const QString PolygonBNPTypeConstructor::descriptiveName() const +{ + return i18n("Polygon by Its Vertices"); +} + +const QString PolygonBNPTypeConstructor::description() const +{ + return i18n("Construct a polygon by giving its vertices"); +} + +const QCString PolygonBNPTypeConstructor::iconFileName( const bool ) const +{ + return "kig_polygon"; +} + +const bool PolygonBNPTypeConstructor::isAlreadySelectedOK( + const std::vector<ObjectCalcer*>& os, const int& pos ) const +{ + if ( pos == 0 && os.size() >= 3 ) return true; + return false; +} + +const int PolygonBNPTypeConstructor::wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument&, + const KigWidget& ) const +{ + int count=os.size() - 1; + + for ( int i = 0; i <= count; i++ ) + { + if ( ! ( os[i]->imp()->inherits( PointImp::stype() ) ) ) return ArgsParser::Invalid; + } + if ( count < 3 ) return ArgsParser::Valid; + if ( os[0] == os[count] ) return ArgsParser::Complete; + return ArgsParser::Valid; +} + +void PolygonBNPTypeConstructor::handleArgs( + const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& v ) const +{ + std::vector<ObjectHolder*> bos = build( os, d.document(), v ); + for ( std::vector<ObjectHolder*>::iterator i = bos.begin(); + i != bos.end(); ++i ) + { + (*i)->calc( d.document() ); + } + + d.addObjects( bos ); +} + +void PolygonBNPTypeConstructor::handlePrelim( + KigPainter& p, const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& + ) const +{ + uint count = os.size(); + if ( count < 2 ) return; + + for ( uint i = 0; i < count; i++ ) + { + assert ( os[i]->imp()->inherits( PointImp::stype() ) ); + } + + std::vector<ObjectCalcer*> args = os; + p.setBrushStyle( Qt::NoBrush ); + p.setBrushColor( Qt::red ); + p.setPen( QPen ( Qt::red, 1) ); + p.setWidth( -1 ); // -1 means the default width for the object being + // drawn.. + + ObjectDrawer drawer( Qt::red ); + drawprelim( drawer, p, args, d ); +} + +QString PolygonBNPTypeConstructor::useText( const ObjectCalcer&, const std::vector<ObjectCalcer*>& os, + const KigDocument&, const KigWidget& ) const +{ + if ( os.size() > 3 ) + return i18n("... with this vertex (click on the first vertex to terminate construction)"); + else return i18n("Construct a polygon with this vertex"); +} + +QString PolygonBNPTypeConstructor::selectStatement( + const std::vector<ObjectCalcer*>&, const KigDocument&, + const KigWidget& ) const +{ + return i18n("Select a point to be a vertex of the new polygon..."); +} + +void PolygonBNPTypeConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + if ( parents.size() < 2 ) return; + + std::vector<Coordinate> points; + + for ( uint i = 0; i < parents.size(); ++i ) + { + const Coordinate vertex = + static_cast<const PointImp*>( parents[i]->imp() )->coordinate(); + points.push_back( vertex ); + } + + if ( parents.size() == 2 ) + { + SegmentImp segment = SegmentImp( points[0], points[1] ); + drawer.draw( segment, p, true ); + } else { + PolygonImp polygon = PolygonImp( points ); + drawer.draw( polygon, p, true ); + } +} + +std::vector<ObjectHolder*> PolygonBNPTypeConstructor::build( const std::vector<ObjectCalcer*>& parents, KigDocument&, KigWidget& ) const +{ + uint count = parents.size() - 1; + assert ( count >= 3 ); + std::vector<ObjectCalcer*> args; + for ( uint i = 0; i < count; ++i ) args.push_back( parents[i] ); + ObjectTypeCalcer* calcer = new ObjectTypeCalcer( mtype, args ); + ObjectHolder* h = new ObjectHolder( calcer ); + std::vector<ObjectHolder*> ret; + ret.push_back( h ); + return ret; +} + +void PolygonBNPTypeConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool PolygonBNPTypeConstructor::isTransform() const +{ + return false; +} + +/* + * construction of polygon vertices + */ + +static const struct ArgsParser::spec argsspecpv[] = +{ + { PolygonImp::stype(), I18N_NOOP( "Polygon" ), + I18N_NOOP( "Construct the vertices of this polygon..." ), true } +}; + +PolygonVertexTypeConstructor::PolygonVertexTypeConstructor() + : StandardConstructorBase( I18N_NOOP( "Vertices of a Polygon" ), + I18N_NOOP( "The vertices of a polygon." ), + "polygonvertices", margsparser ), + mtype( PolygonVertexType::instance() ), + margsparser( argsspecpv, 1 ) +{ +} + +PolygonVertexTypeConstructor::~PolygonVertexTypeConstructor() +{ +} + +void PolygonVertexTypeConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + if ( parents.size() != 1 ) return; + + const PolygonImp* polygon = dynamic_cast<const PolygonImp*>( parents.front()->imp() ); + const std::vector<Coordinate> points = polygon->points(); + + int sides = points.size(); + for ( int i = 0; i < sides; ++i ) + { + PointImp point = PointImp( points[i] ); + drawer.draw( point, p, true ); + } +} + +std::vector<ObjectHolder*> PolygonVertexTypeConstructor::build( const std::vector<ObjectCalcer*>& parents, KigDocument&, KigWidget& ) const +{ + std::vector<ObjectHolder*> ret; + assert( parents.size() == 1 ); + const PolygonImp* polygon = dynamic_cast<const PolygonImp*>( parents.front()->imp() ); + const std::vector<Coordinate> points = polygon->points(); + + int sides = points.size(); + + for ( int i = 0; i < sides; ++i ) + { + ObjectConstCalcer* d = new ObjectConstCalcer( new IntImp( i ) ); + std::vector<ObjectCalcer*> args( parents ); + args.push_back( d ); + ret.push_back( new ObjectHolder( new ObjectTypeCalcer( mtype, args ) ) ); + } + return ret; +} + +void PolygonVertexTypeConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool PolygonVertexTypeConstructor::isTransform() const +{ + return false; +} + +/* + * construction of polygon sides + */ + +static const struct ArgsParser::spec argsspecps[] = +{ + { PolygonImp::stype(), I18N_NOOP( "Polygon" ), + I18N_NOOP( "Construct the sides of this polygon..." ), false } +}; + +PolygonSideTypeConstructor::PolygonSideTypeConstructor() + : StandardConstructorBase( I18N_NOOP( "Sides of a Polygon" ), + I18N_NOOP( "The sides of a polygon." ), + "polygonsides", margsparser ), + mtype( PolygonSideType::instance() ), + margsparser( argsspecps, 1 ) +{ +} + +PolygonSideTypeConstructor::~PolygonSideTypeConstructor() +{ +} + +void PolygonSideTypeConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + if ( parents.size() != 1 ) return; + + const PolygonImp* polygon = dynamic_cast<const PolygonImp*>( parents.front()->imp() ); + const std::vector<Coordinate> points = polygon->points(); + + uint sides = points.size(); + for ( uint i = 0; i < sides; ++i ) + { + uint nexti = ( i + 1 < sides )?(i + 1):0; + SegmentImp segment = SegmentImp( points[i], points[nexti] ); + drawer.draw( segment, p, true ); + } +} + +std::vector<ObjectHolder*> PolygonSideTypeConstructor::build( const std::vector<ObjectCalcer*>& parents, KigDocument&, KigWidget& ) const +{ + std::vector<ObjectHolder*> ret; + assert( parents.size() == 1 ); + const PolygonImp* polygon = dynamic_cast<const PolygonImp*>( parents.front()->imp() ); + const std::vector<Coordinate> points = polygon->points(); + + uint sides = points.size(); + + for ( uint i = 0; i < sides; ++i ) + { + ObjectConstCalcer* d = new ObjectConstCalcer( new IntImp( i ) ); + std::vector<ObjectCalcer*> args( parents ); + args.push_back( d ); + ret.push_back( new ObjectHolder( new ObjectTypeCalcer( mtype, args ) ) ); + } + return ret; +} + +void PolygonSideTypeConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool PolygonSideTypeConstructor::isTransform() const +{ + return false; +} + +/* + * polygon by center and vertex + */ + +PolygonBCVConstructor::PolygonBCVConstructor() + : mtype( PolygonBCVType::instance() ) +{ +} + +PolygonBCVConstructor::~PolygonBCVConstructor() +{ +} + +const QString PolygonBCVConstructor::descriptiveName() const +{ + return i18n("Regular Polygon with Given Center"); +} + +const QString PolygonBCVConstructor::description() const +{ + return i18n("Construct a regular polygon with a given center and vertex"); +} + +const QCString PolygonBCVConstructor::iconFileName( const bool ) const +{ + return "hexagonbcv"; +} + +const bool PolygonBCVConstructor::isAlreadySelectedOK( + const std::vector<ObjectCalcer*>&, const int& ) const +{ + return false; +} + +const int PolygonBCVConstructor::wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument&, + const KigWidget& ) const +{ + if ( os.size() > 3 ) return ArgsParser::Invalid; + + uint imax = ( os.size() <= 2) ? os.size() : 2; + for ( uint i = 0; i < imax; ++i ) + if ( ! ( os[i]->imp()->inherits( PointImp::stype() ) ) ) return ArgsParser::Invalid; + + if ( os.size() < 3 ) return ArgsParser::Valid; + + if ( ! ( os[2]->imp()->inherits( BogusPointImp::stype() ) ) ) + return ArgsParser::Invalid; + + return ArgsParser::Complete; +} + +void PolygonBCVConstructor::handleArgs( + const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& v ) const +{ + std::vector<ObjectHolder*> bos = build( os, d.document(), v ); + for ( std::vector<ObjectHolder*>::iterator i = bos.begin(); + i != bos.end(); ++i ) + { + (*i)->calc( d.document() ); + } + + d.addObjects( bos ); +} + +void PolygonBCVConstructor::handlePrelim( + KigPainter& p, const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& + ) const +{ + if ( os.size() < 2 ) return; + + for ( uint i = 0; i < 2; i++ ) + { + assert ( os[i]->imp()->inherits( PointImp::stype() ) ); + } + + Coordinate c = static_cast<const PointImp*>( os[0]->imp() )->coordinate(); + Coordinate v = static_cast<const PointImp*>( os[1]->imp() )->coordinate(); + + int nsides = 6; + bool moreinfo = false; + int winding = 0; // 0 means allow winding > 1 + if ( os.size() == 3 ) + { + assert ( os[2]->imp()->inherits( BogusPointImp::stype() ) ); + Coordinate cntrl = static_cast<const PointImp*>( os[2]->imp() )->coordinate(); + nsides = computeNsides( c, v, cntrl, winding ); + moreinfo = true; + } + + std::vector<ObjectCalcer*> args; + args.push_back( os[0] ); + args.push_back( os[1] ); + ObjectConstCalcer* ns = new ObjectConstCalcer( new IntImp( nsides ) ); + args.push_back( ns ); + if ( winding > 1 ) + { + ns = new ObjectConstCalcer( new IntImp( winding ) ); + args.push_back( ns ); + } + + p.setBrushStyle( Qt::NoBrush ); + p.setBrushColor( Qt::red ); + p.setPen( QPen ( Qt::red, 1) ); + p.setWidth( -1 ); // -1 means the default width for the object being + // drawn.. + + ObjectDrawer drawer( Qt::red ); + drawprelim( drawer, p, args, d ); + if ( moreinfo ) + { + p.setPointStyle( 1 ); + p.setWidth( 6 ); + double ro = 1.0/(2.5); + Coordinate where = getRotatedCoord( c, (1-ro)*c+ro*v, 4*M_PI/5.0 ); + PointImp ptn = PointImp( where ); + TextImp text = TextImp( "(5,2)", where, false ); + ptn.draw( p ); + text.draw( p ); + for ( int i = 3; i < 9; ++i ) + { + where = getRotatedCoord( c, v, 2.0*M_PI/i ); + ptn = PointImp( where ); + ptn.draw( p ); + if ( i > 5 ) continue; + text = TextImp( QString( "(%1)" ).arg(i), where, false ); + text.draw( p ); + } + p.setStyle( Qt::DotLine ); + p.setWidth( 1 ); + double radius = ( v - c ).length(); + CircleImp circle = CircleImp( c, radius ); + circle.draw( p ); + for ( int i = 2; i < 5; i++ ) + { + ro = 1.0/(i+0.5); + CircleImp circle = CircleImp( c, ro*radius ); + circle.draw( p ); + } + } + delete_all( args.begin() + 2, args.end() ); +} + +std::vector<ObjectHolder*> PolygonBCVConstructor::build( const std::vector<ObjectCalcer*>& parents, KigDocument&, KigWidget& ) const +{ + assert ( parents.size() == 3 ); + std::vector<ObjectCalcer*> args; + + Coordinate c = static_cast<const PointImp*>( parents[0]->imp() )->coordinate(); + Coordinate v = static_cast<const PointImp*>( parents[1]->imp() )->coordinate(); + Coordinate cntrl = static_cast<const PointImp*>( parents[2]->imp() )->coordinate(); + + args.push_back( parents[0] ); + args.push_back( parents[1] ); + int winding = 0; + int nsides = computeNsides( c, v, cntrl, winding ); + ObjectConstCalcer* d = new ObjectConstCalcer( new IntImp( nsides ) ); + args.push_back( d ); + if ( winding > 1 ) + { + d = new ObjectConstCalcer( new IntImp( winding ) ); + args.push_back( d ); + } + + ObjectTypeCalcer* calcer = new ObjectTypeCalcer( mtype, args ); + ObjectHolder* h = new ObjectHolder( calcer ); + std::vector<ObjectHolder*> ret; + ret.push_back( h ); + return ret; +} + +QString PolygonBCVConstructor::useText( const ObjectCalcer&, const std::vector<ObjectCalcer*>& os, + const KigDocument&, const KigWidget& ) const +{ + switch ( os.size() ) + { + case 1: + return i18n( "Construct a regular polygon with this center" ); + break; + + case 2: + return i18n( "Construct a regular polygon with this vertex" ); + break; + + case 3: + Coordinate c = static_cast<const PointImp*>( os[0]->imp() )->coordinate(); + Coordinate v = static_cast<const PointImp*>( os[1]->imp() )->coordinate(); + Coordinate cntrl = static_cast<const PointImp*>( os[2]->imp() )->coordinate(); + int winding = 0; + int nsides = computeNsides( c, v, cntrl, winding ); + + if ( winding > 1 ) + { + QString result = QString( + i18n( "Adjust the number of sides (%1/%2)" ) + ).arg( nsides ).arg( winding ); + return result; + } else + { + QString result = QString( + i18n( "Adjust the number of sides (%1)" ) + ).arg( nsides ); + return result; + } + break; + } + + return ""; +} + +QString PolygonBCVConstructor::selectStatement( + const std::vector<ObjectCalcer*>& os, const KigDocument&, + const KigWidget& ) const +{ + switch ( os.size() ) + { + case 1: + return i18n( "Select the center of the new polygon..." ); + break; + + case 2: + return i18n( "Select a vertex for the new polygon..." ); + break; + + case 3: + return i18n( "Move the cursor to get the desired number of sides..." ); + break; + } + + return ""; +} + +void PolygonBCVConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& doc ) const +{ + if ( parents.size() < 3 || parents.size() > 4 ) return; + + assert ( parents[0]->imp()->inherits( PointImp::stype() ) && + parents[1]->imp()->inherits( PointImp::stype() ) && + parents[2]->imp()->inherits( IntImp::stype() ) ); + + if ( parents.size() == 4 ) + assert ( parents[3]->imp()->inherits( IntImp::stype() ) ); + + Args args; + std::transform( parents.begin(), parents.end(), + std::back_inserter( args ), std::mem_fun( &ObjectCalcer::imp ) ); + + ObjectImp* data = mtype->calc( args, doc ); + drawer.draw( *data, p, true ); + delete data; + data = 0; +} + +void PolygonBCVConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool PolygonBCVConstructor::isTransform() const +{ + return false; +} + +Coordinate PolygonBCVConstructor::getRotatedCoord( const Coordinate& c, + const Coordinate& v, double alpha ) const +{ + double cosalpha = cos(alpha); + double sinalpha = sin(alpha); + double dx = v.x - c.x; + double dy = v.y - c.y; + return c + Coordinate( cosalpha*dx - sinalpha*dy, sinalpha*dx + cosalpha*dy ); +} + +int PolygonBCVConstructor::computeNsides ( const Coordinate& c, + const Coordinate& v, const Coordinate& cntrl, int& winding ) const +{ + Coordinate lvect = v - c; + Coordinate rvect = cntrl - c; + + double angle = atan2( rvect.y, rvect.x ) - atan2( lvect.y, lvect.x ); + angle = fabs( angle/(2*M_PI) ); + while ( angle > 1 ) angle -= 1; + if ( angle > 0.5 ) angle = 1 - angle; + + double realsides = 1.0/angle; // this is bigger that 2 + if ( angle == 0. ) realsides = 3; + if ( winding <= 0 ) // free to compute winding + { + winding = 1; + double ratio = lvect.length()/rvect.length(); + winding = int ( ratio ); + if ( winding < 1 ) winding = 1; + if ( winding > 50 ) winding = 50; + } + int nsides = int( winding*realsides + 0.5 ); // nsides/winding should be reduced! + if ( nsides > 100 ) nsides = 100; // well, 100 seems large enough! + if ( nsides < 3 ) nsides = 3; + while ( !relativePrimes ( nsides, winding ) ) ++nsides; + return nsides; +} + +/* + * ConicConic intersection... + */ + +static const ArgsParser::spec argsspectc[] = { + { ConicImp::stype(), "SHOULD NOT BE SEEN", "SHOULD NOT BE SEEN", true }, + { ConicImp::stype(), "SHOULD NOT BE SEEN", "SHOULD NOT BE SEEN", true } +}; + +ConicConicIntersectionConstructor::ConicConicIntersectionConstructor() + : StandardConstructorBase( "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection", mparser ), + mparser( argsspectc, 2 ) +{ +} + +ConicConicIntersectionConstructor::~ConicConicIntersectionConstructor() +{ +} + +void ConicConicIntersectionConstructor::drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + if ( parents.size() != 2 ) return; + assert ( parents[0]->imp()->inherits( ConicImp::stype() ) && + parents[1]->imp()->inherits( ConicImp::stype() ) ); + const ConicCartesianData conica = + static_cast<const ConicImp*>( parents[0]->imp() )->cartesianData(); + const ConicCartesianData conicb = + static_cast<const ConicImp*>( parents[1]->imp() )->cartesianData(); + bool ok = true; + for ( int wr = -1; wr < 2; wr += 2 ) + { + LineData radical = calcConicRadical( conica, conicb, wr, 1, ok ); + if ( ok ) + { + for ( int wi = -1; wi < 2; wi += 2 ) + { + Coordinate c = calcConicLineIntersect( conica, radical, 0.0, wi ); + if ( c.valid() ) { + PointImp pi( c ); + drawer.draw( pi, p, true ); + } + }; + }; + }; +} + +std::vector<ObjectHolder*> ConicConicIntersectionConstructor::build( + const std::vector<ObjectCalcer*>& os, KigDocument& doc, KigWidget& ) const +{ + assert( os.size() == 2 ); + std::vector<ObjectHolder*> ret; + ObjectCalcer* conica = os[0]; + ObjectConstCalcer* zeroindexdo = new ObjectConstCalcer( new IntImp( 1 ) ); + + for ( int wr = -1; wr < 2; wr += 2 ) + { + std::vector<ObjectCalcer*> args = os; + args.push_back( new ObjectConstCalcer( new IntImp( wr ) ) ); + args.push_back( zeroindexdo ); + ObjectTypeCalcer* radical = + new ObjectTypeCalcer( ConicRadicalType::instance(), args ); + radical->calc( doc ); + for ( int wi = -1; wi < 2; wi += 2 ) + { + args.clear(); + args.push_back( conica ); + args.push_back( radical ); + args.push_back( new ObjectConstCalcer( new IntImp( wi ) ) ); + ret.push_back( + new ObjectHolder( + new ObjectTypeCalcer( + ConicLineIntersectionType::instance(), args ) ) ); + }; + }; + return ret; +} + +void ConicConicIntersectionConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool ConicConicIntersectionConstructor::isTransform() const +{ + return false; +} + +ConicLineIntersectionConstructor::ConicLineIntersectionConstructor() + : MultiObjectTypeConstructor( + ConicLineIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection", -1, 1 ) +{ +} + +ConicLineIntersectionConstructor::~ConicLineIntersectionConstructor() +{ +} + +ArcLineIntersectionConstructor::ArcLineIntersectionConstructor() + : MultiObjectTypeConstructor( + ArcLineIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection", -1, 1 ) +{ +} + +ArcLineIntersectionConstructor::~ArcLineIntersectionConstructor() +{ +} + +QString ConicRadicalConstructor::useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>&, + const KigDocument&, const KigWidget& ) const +{ + if ( o.imp()->inherits( CircleImp::stype() ) ) + return i18n( "Construct the Radical Lines of This Circle" ); + else + return i18n( "Construct the Radical Lines of This Conic" ); +} + +/* + * generic affinity and generic projectivity. A unique affinity can be + * obtained by specifying the image of three points (four for projectivity) + * in the end we need, besides the object to be transformed, a total of + * six point or (alternatively) two triangles; our affinity will map the + * first triangle onto the second with corresponding ordering of their + * vertices. Since we allow for two different ways of specifying the six + * points we shall use a Generic constructor, like that for intersections. + */ + +GenericAffinityConstructor::GenericAffinityConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Generic Affinity" ), + I18N_NOOP( "The unique affinity that maps three points (or a triangle) onto three other points (or a triangle)" ), + "genericaffinity" ) +{ + SimpleObjectTypeConstructor* b2tr = + new SimpleObjectTypeConstructor( + AffinityB2TrType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "genericaffinity" ); + + SimpleObjectTypeConstructor* gi3p = + new SimpleObjectTypeConstructor( + AffinityGI3PType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "genericaffinity" ); + + merge( b2tr ); + merge( gi3p ); +} + +GenericAffinityConstructor::~GenericAffinityConstructor() {} + +GenericProjectivityConstructor::GenericProjectivityConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Generic Projective Transformation" ), + I18N_NOOP( "The unique projective transformation that maps four points (or a quadrilateral) onto four other points (or a quadrilateral)" ), + "genericprojectivity" ) +{ + SimpleObjectTypeConstructor* b2qu = + new SimpleObjectTypeConstructor( + ProjectivityB2QuType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "genericprojectivity" ); + + SimpleObjectTypeConstructor* gi4p = + new SimpleObjectTypeConstructor( + ProjectivityGI4PType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "genericprojectivity" ); + + merge( b2qu ); + merge( gi4p ); +} + +GenericProjectivityConstructor::~GenericProjectivityConstructor() {} + +/* + * inversion of points, lines with respect to a circle + */ + +InversionConstructor::InversionConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Inversion of Point, Line or Circle" ), + I18N_NOOP( "The inversion of a point, line or circle with respect to a circle" ), + "inversion" ) +{ + SimpleObjectTypeConstructor* pointobj = + new SimpleObjectTypeConstructor( + InvertPointType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "inversion" ); + + SimpleObjectTypeConstructor* lineobj = + new SimpleObjectTypeConstructor( + InvertLineType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "inversion" ); + + SimpleObjectTypeConstructor* segmentobj = + new SimpleObjectTypeConstructor( + InvertSegmentType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "inversion" ); + + SimpleObjectTypeConstructor* circleobj = + new SimpleObjectTypeConstructor( + InvertCircleType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "inversion" ); + + SimpleObjectTypeConstructor* arcobj = + new SimpleObjectTypeConstructor( + InvertArcType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "inversion" ); + + merge( arcobj ); + merge( circleobj ); + merge( pointobj ); + merge( segmentobj ); + merge( lineobj ); +} + +InversionConstructor::~InversionConstructor() {} + +/* + * Transport of Measure + */ + +MeasureTransportConstructor::MeasureTransportConstructor() + : mtype( MeasureTransportType::instance() ) +{ +} + +MeasureTransportConstructor::~MeasureTransportConstructor() +{ +} + +const QString MeasureTransportConstructor::descriptiveName() const +{ + return i18n("Measure Transport"); +} + +const QString MeasureTransportConstructor::description() const +{ + return i18n("Transport the measure of a segment or arc over a line or circle."); +} + +const QCString MeasureTransportConstructor::iconFileName( const bool ) const +{ + return "measuretransport"; +} + +const bool MeasureTransportConstructor::isAlreadySelectedOK( + const std::vector<ObjectCalcer*>&, const int& ) const +{ + return false; +} + +/* + * we want the arguments in the exact order, this makes + * the code simpler, but I guess it is also less confusing + * to the user + */ + +const int MeasureTransportConstructor::wantArgs( + const std::vector<ObjectCalcer*>& os, + const KigDocument&, + const KigWidget& ) const +{ + if ( os.size() == 0 ) return ArgsParser::Valid; + + if ( ! os[0]->imp()->inherits( SegmentImp::stype() ) && + ! os[0]->imp()->inherits( ArcImp::stype() ) ) + return ArgsParser::Invalid; + + if ( os.size() == 1 ) return ArgsParser::Valid; + + if ( ! os[1]->imp()->inherits( LineImp::stype() ) && + ! os[1]->imp()->inherits( CircleImp::stype() ) ) + return ArgsParser::Invalid; + + if ( os.size() == 2 ) return ArgsParser::Valid; + + if ( ! os[2]->imp()->inherits( PointImp::stype() ) ) + return ArgsParser::Invalid; + + // we here use the "isPointOnCurve", which relies on + // "by construction" incidence, instead of a numerical + // check + if ( ! isPointOnCurve( os[2], os[1] ) ) + return ArgsParser::Invalid; + + if ( os.size() == 3 ) return ArgsParser::Complete; + + return ArgsParser::Invalid; +} + +void MeasureTransportConstructor::handleArgs( + const std::vector<ObjectCalcer*>& os, KigPart& d, + KigWidget& v ) const +{ + std::vector<ObjectHolder*> bos = build( os, d.document(), v ); + for ( std::vector<ObjectHolder*>::iterator i = bos.begin(); + i != bos.end(); ++i ) + { + (*i)->calc( d.document() ); + } + + d.addObjects( bos ); +} + +void MeasureTransportConstructor::handlePrelim( + KigPainter& p, const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& + ) const +{ + p.setBrushStyle( Qt::NoBrush ); + p.setBrushColor( Qt::red ); + p.setPen( QPen ( Qt::red, 1) ); + p.setWidth( -1 ); // -1 means the default width for the object being + // drawn.. + + ObjectDrawer drawer( Qt::red ); + drawprelim( drawer, p, os, d ); +} + +void MeasureTransportConstructor::drawprelim( const ObjectDrawer& drawer, + KigPainter& p, + const std::vector<ObjectCalcer*>& parents, + const KigDocument& doc ) const +{ + Args args; + using namespace std; + transform( parents.begin(), parents.end(), + back_inserter( args ), mem_fun( &ObjectCalcer::imp ) ); + ObjectImp* data = mtype->calc( args, doc ); + drawer.draw( *data, p, true ); + delete data; +} + +QString MeasureTransportConstructor::useText( const ObjectCalcer& o, + const std::vector<ObjectCalcer*>& os, + const KigDocument&, const KigWidget& ) const +{ + if ( o.imp()->inherits( SegmentImp::stype() ) ) + return i18n("Segment to transport"); + if ( o.imp()->inherits( ArcImp::stype() ) ) + return i18n("Arc to transport"); + if ( o.imp()->inherits( LineImp::stype() ) ) + return i18n("Transport a measure on this line"); + if ( o.imp()->inherits( CircleImp::stype() ) ) + return i18n("Transport a measure on this circle"); + if ( o.imp()->inherits( PointImp::stype() ) ) + { + if ( os[1]->imp()->inherits( CircleImp::stype() ) ) + return i18n("Start transport from this point of the circle"); + if ( os[1]->imp()->inherits( LineImp::stype() ) ) + return i18n("Start transport from this point of the line"); + else + return i18n("Start transport from this point of the curve"); + // well, this isn't impemented yet, should never get here + } + return ""; +} + +QString MeasureTransportConstructor::selectStatement( + const std::vector<ObjectCalcer*>&, const KigDocument&, + const KigWidget& ) const +{ +//TODO + return i18n("Select a point to be a vertex of the new polygon..."); +} + +std::vector<ObjectHolder*> MeasureTransportConstructor::build( + const std::vector<ObjectCalcer*>& parents, + KigDocument&, KigWidget& ) const +{ + assert ( parents.size() == 3 ); +// std::vector<ObjectCalcer*> args; +// for ( uint i = 0; i < count; ++i ) args.push_back( parents[i] ); + ObjectTypeCalcer* calcer = new ObjectTypeCalcer( mtype, parents ); + ObjectHolder* h = new ObjectHolder( calcer ); + std::vector<ObjectHolder*> ret; + ret.push_back( h ); + return ret; +} + +void MeasureTransportConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool MeasureTransportConstructor::isTransform() const +{ + return false; +} + +/* + * Generic intersection + */ + +GenericIntersectionConstructor::GenericIntersectionConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Intersect" ), + I18N_NOOP( "The intersection of two objects" ), + "curvelineintersection" ) +{ + // intersection type.. + // There is one "toplevel" object_constructor, that is composed + // of multiple subconstructors.. First we build the + // subconstructors: + SimpleObjectTypeConstructor* lineline = + new SimpleObjectTypeConstructor( + LineLineIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection" ); + + ObjectConstructor* lineconic = + new ConicLineIntersectionConstructor(); + + ObjectConstructor* arcline = + new ArcLineIntersectionConstructor(); + + MultiObjectTypeConstructor* linecubic = + new MultiObjectTypeConstructor( + LineCubicIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection", 1, 2, 3 ); + + ObjectConstructor* conicconic = + new ConicConicIntersectionConstructor(); + + MultiObjectTypeConstructor* circlecircle = + new MultiObjectTypeConstructor( + CircleCircleIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "circlecircleintersection", -1, 1 ); + + SimpleObjectTypeConstructor* polygonline = + new SimpleObjectTypeConstructor( + PolygonLineIntersectionType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "curvelineintersection" ); + + merge( lineline ); + merge( circlecircle ); + merge( lineconic ); + merge( linecubic ); + merge( conicconic ); + merge( arcline ); + merge( polygonline ); +} + +GenericIntersectionConstructor::~GenericIntersectionConstructor() +{ +} + +bool GenericIntersectionConstructor::isIntersection() const +{ + return true; +} + +QString GenericIntersectionConstructor::useText( + const ObjectCalcer& o, const std::vector<ObjectCalcer*>& os, + const KigDocument&, const KigWidget& ) const +{ + QString preamble; + switch (os.size()) + { + case 1: + if ( o.imp()->inherits( CircleImp::stype() ) ) + return i18n( "Intersect this Circle" ); + else if ( o.imp()->inherits( ConicImp::stype() ) ) + return i18n( "Intersect this Conic" ); + else if ( o.imp()->inherits( AbstractLineImp::stype() ) ) + return i18n( "Intersect this Line" ); + else if ( o.imp()->inherits( CubicImp::stype() ) ) + return i18n( "Intersect this Cubic Curve" ); + else if ( o.imp()->inherits( ArcImp::stype() ) ) + return i18n( "Intersect this Arc" ); + else if ( o.imp()->inherits( PolygonImp::stype() ) ) + return i18n( "Intersect this Polygon" ); + else assert( false ); + break; + case 2: + if ( o.imp()->inherits( CircleImp::stype() ) ) + return i18n( "with this Circle" ); + else if ( o.imp()->inherits( ConicImp::stype() ) ) + return i18n( "with this Conic" ); + else if ( o.imp()->inherits( AbstractLineImp::stype() ) ) + return i18n( "with this Line" ); + else if ( o.imp()->inherits( CubicImp::stype() ) ) + return i18n( "with this Cubic Curve" ); + else if ( o.imp()->inherits( ArcImp::stype() ) ) + return i18n( "with this Arc" ); + else if ( o.imp()->inherits( PolygonImp::stype() ) ) + return i18n( "with this Polygon" ); + else assert( false ); + break; + } + + return QString::null; +} + +static const ArgsParser::spec argsspecMidPointOfTwoPoints[] = +{ + { PointImp::stype(), I18N_NOOP( "Construct Midpoint of This Point and Another One" ), + I18N_NOOP( "Select the first of the points of which you want to construct the midpoint..." ), false }, + { PointImp::stype(), I18N_NOOP( "Construct the midpoint of this point and another one" ), + I18N_NOOP( "Select the other of the points of which to construct the midpoint..." ), false } +}; + +MidPointOfTwoPointsConstructor::MidPointOfTwoPointsConstructor() + : StandardConstructorBase( "Mid Point", + "Construct the midpoint of two points", + "bisection", mparser ), + mparser( argsspecMidPointOfTwoPoints, 2 ) +{ +} + +MidPointOfTwoPointsConstructor::~MidPointOfTwoPointsConstructor() +{ +} + +void MidPointOfTwoPointsConstructor::drawprelim( + const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const +{ + if ( parents.size() != 2 ) return; + assert( parents[0]->imp()->inherits( PointImp::stype() ) ); + assert( parents[1]->imp()->inherits( PointImp::stype() ) ); + const Coordinate m = + ( static_cast<const PointImp*>( parents[0]->imp() )->coordinate() + + static_cast<const PointImp*>( parents[1]->imp() )->coordinate() ) / 2; + drawer.draw( PointImp( m ), p, true ); +} + +std::vector<ObjectHolder*> MidPointOfTwoPointsConstructor::build( + const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& ) const +{ + ObjectTypeCalcer* seg = new ObjectTypeCalcer( SegmentABType::instance(), os ); + seg->calc( d ); + int index = seg->imp()->propertiesInternalNames().findIndex( "mid-point" ); + assert( index != -1 ); + ObjectPropertyCalcer* prop = new ObjectPropertyCalcer( seg, index ); + prop->calc( d ); + std::vector<ObjectHolder*> ret; + ret.push_back( new ObjectHolder( prop ) ); + return ret; +} + +void MidPointOfTwoPointsConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool MidPointOfTwoPointsConstructor::isTransform() const +{ + return false; +} + +TestConstructor::TestConstructor( const ArgsParserObjectType* type, const char* descname, + const char* desc, const char* iconfile ) + : StandardConstructorBase( descname, desc, iconfile, type->argsParser() ), + mtype( type ) +{ +} + +TestConstructor::~TestConstructor() +{ +} + +void TestConstructor::drawprelim( const ObjectDrawer&, KigPainter&, const std::vector<ObjectCalcer*>&, + const KigDocument& ) const +{ + // not used, only here because of the wrong + // ObjectConstructor-GUIAction design. See the TODO +} + +std::vector<ObjectHolder*> TestConstructor::build( const std::vector<ObjectCalcer*>&, KigDocument&, + KigWidget& ) const +{ + // not used, only here because of the wrong + // ObjectConstructor-GUIAction design. See the TODO + std::vector<ObjectHolder*> ret; + return ret; +} + +void TestConstructor::plug( KigPart*, KigGUIAction* ) +{ +} + +bool TestConstructor::isTransform() const +{ + return false; +} + +bool TestConstructor::isTest() const +{ + return true; +} + +BaseConstructMode* TestConstructor::constructMode( KigPart& doc ) +{ + return new TestConstructMode( doc, mtype ); +} + +const int TestConstructor::wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& v ) const +{ + int ret = StandardConstructorBase::wantArgs( os, d, v ); + if ( ret == ArgsParser::Complete ) ret = ArgsParser::Valid; + return ret; +} + +QString GenericIntersectionConstructor::selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument&, + const KigWidget& ) const +{ + if ( sel.size() == 0 ) + return i18n( "Select the first object to intersect..." ); + else + return i18n( "Select the second object to intersect..." ); +} + +TangentConstructor::TangentConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Tangent" ), + I18N_NOOP( "The line tangent to a curve" ), + "tangent" ) +{ + SimpleObjectTypeConstructor* conic = + new SimpleObjectTypeConstructor( + TangentConicType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "tangentconic" ); + + SimpleObjectTypeConstructor* arc = + new SimpleObjectTypeConstructor( + TangentArcType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "tangentarc" ); + + SimpleObjectTypeConstructor* cubic = + new SimpleObjectTypeConstructor( + TangentCubicType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "tangentcubic" ); + + SimpleObjectTypeConstructor* curve = + new SimpleObjectTypeConstructor( + TangentCurveType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "tangentcurve" ); + + merge( conic ); + merge( arc ); + merge( cubic ); + merge( curve ); +} + +TangentConstructor::~TangentConstructor() +{ +} + +QString TangentConstructor::useText( + const ObjectCalcer& o, const std::vector<ObjectCalcer*>&, + const KigDocument&, const KigWidget& ) const +{ + if ( o.imp()->inherits( CircleImp::stype() ) ) + return i18n( "Tangent to This Circle" ); + else if ( o.imp()->inherits( ConicImp::stype() ) ) + return i18n( "Tangent to This Conic" ); + else if ( o.imp()->inherits( ArcImp::stype() ) ) + return i18n( "Tangent to This Arc" ); + else if ( o.imp()->inherits( CubicImp::stype() ) ) + return i18n( "Tangent to This Cubic Curve" ); + else if ( o.imp()->inherits( CurveImp::stype() ) ) + return i18n( "Tangent to This Curve" ); + else if ( o.imp()->inherits( PointImp::stype() ) ) + return i18n( "Tangent at This Point" ); +// else assert( false ); + return QString::null; +} + +//QString TangentConstructor::selectStatement( +// const std::vector<ObjectCalcer*>& sel, const KigDocument&, +// const KigWidget& ) const +//{ +// if ( sel.size() == 0 ) +// return i18n( "Select the object..." ); +// else +// return i18n( "Select the point for the tangent to go through..." ); +//} + +/* + * center of curvature of a curve + */ + +CocConstructor::CocConstructor() + : MergeObjectConstructor( + I18N_NOOP( "Center Of Curvature" ), + I18N_NOOP( "The center of the osculating circle to a curve" ), + "centerofcurvature" ) +{ + SimpleObjectTypeConstructor* conic = + new SimpleObjectTypeConstructor( + CocConicType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "cocconic" ); + + SimpleObjectTypeConstructor* cubic = + new SimpleObjectTypeConstructor( + CocCubicType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "coccubic" ); + + SimpleObjectTypeConstructor* curve = + new SimpleObjectTypeConstructor( + CocCurveType::instance(), + "SHOULDNOTBESEEN", "SHOULDNOTBESEEN", + "coccurve" ); + + merge( conic ); + merge( cubic ); + merge( curve ); +} + +CocConstructor::~CocConstructor() +{ +} + +QString CocConstructor::useText( + const ObjectCalcer& o, const std::vector<ObjectCalcer*>&, + const KigDocument&, const KigWidget& ) const +{ + if ( o.imp()->inherits( ConicImp::stype() ) ) + return i18n( "Center of Curvature of This Conic" ); + else if ( o.imp()->inherits( CubicImp::stype() ) ) + return i18n( "Center of Curvature of This Cubic Curve" ); + else if ( o.imp()->inherits( CurveImp::stype() ) ) + return i18n( "Center of Curvature of This Curve" ); + else if ( o.imp()->inherits( PointImp::stype() ) ) + return i18n( "Center of Curvature at This Point" ); + return QString::null; +} + +bool relativePrimes( int n, int p ) +{ + if ( p > n ) return relativePrimes( p, n ); + assert ( p >= 0 ); + if ( p == 0 ) return false; + if ( p == 1 ) return true; + int d = int( n/p ); + return relativePrimes( p, n-d*p ); +} + +//QString CocConstructor::selectStatement( +// const std::vector<ObjectCalcer*>& sel, const KigDocument&, +// const KigWidget& ) const +//{ +// if ( sel.size() == 0 ) +// return i18n( "Select the object..." ); +// else +// return i18n( "Select the point where to compute the center of curvature..." ); +//} diff --git a/kig/misc/special_constructors.h b/kig/misc/special_constructors.h new file mode 100644 index 00000000..99760be3 --- /dev/null +++ b/kig/misc/special_constructors.h @@ -0,0 +1,320 @@ +// Copyright (C) 2003 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. + +#ifndef KIG_MISC_SPECIAL_CONSTRUCTORS_H +#define KIG_MISC_SPECIAL_CONSTRUCTORS_H + +#include "object_constructor.h" + +class PolygonVertexTypeConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; + ArgsParser margsparser; +public: + PolygonVertexTypeConstructor(); + ~PolygonVertexTypeConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; +}; + +class PolygonSideTypeConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; + ArgsParser margsparser; +public: + PolygonSideTypeConstructor(); + ~PolygonSideTypeConstructor(); + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; +}; + +class PolygonBNPTypeConstructor + : public ObjectConstructor +{ + const ObjectType* mtype; +public: + PolygonBNPTypeConstructor(); + ~PolygonBNPTypeConstructor(); + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, + const KigWidget& v + ) const; + void handleArgs( const std::vector<ObjectCalcer*>& os, + KigPart& d, + KigWidget& v + ) const; + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + void handlePrelim( KigPainter& p, + const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, + const KigWidget& v + ) const; + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; +}; + +class PolygonBCVConstructor + : public ObjectConstructor +{ + const ObjectType* mtype; +public: + PolygonBCVConstructor(); + ~PolygonBCVConstructor(); + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, + const KigWidget& v + ) const; + void handleArgs( const std::vector<ObjectCalcer*>& os, + KigPart& d, + KigWidget& v + ) const; + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + void handlePrelim( KigPainter& p, + const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, + const KigWidget& v + ) const; + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; + int computeNsides( const Coordinate& c, const Coordinate& v, const Coordinate& cntrl, int& winding ) const; + Coordinate getRotatedCoord( const Coordinate& c1, + const Coordinate& c2, double alpha ) const; +}; + +class MeasureTransportConstructor + : public ObjectConstructor +{ + const ObjectType* mtype; +public: + MeasureTransportConstructor(); + ~MeasureTransportConstructor(); + + const QString descriptiveName() const; + const QString description() const; + const QCString iconFileName( const bool canBeNull = false ) const; + const bool isAlreadySelectedOK( const std::vector<ObjectCalcer*>& os, + const int& ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, + const KigWidget& v + ) const; + void handleArgs( const std::vector<ObjectCalcer*>& os, + KigPart& d, + KigWidget& v + ) const; + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, const KigWidget& v + ) const; + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; + void handlePrelim( KigPainter& p, + const std::vector<ObjectCalcer*>& sel, + const KigDocument& d, + const KigWidget& v + ) const; + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; +}; + +class ConicRadicalConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; + const ArgsParser mparser; +public: + ConicRadicalConstructor(); + ~ConicRadicalConstructor(); + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& v ) const; + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +class LocusConstructor + : public StandardConstructorBase +{ + ArgsParser margsparser; +public: + LocusConstructor(); + ~LocusConstructor(); + /** + * we override the wantArgs() function, since we need to see + * something about the objects that an ArgsParser can't know about, + * namely, whether the first point is a constrained point... + */ + const int wantArgs( + const std::vector<ObjectCalcer*>& os, const KigDocument& d, + const KigWidget& v + ) const; + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& v ) const; + + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + + bool isTransform() const; +}; + +class GenericAffinityConstructor + : public MergeObjectConstructor +{ +public: + GenericAffinityConstructor(); + ~GenericAffinityConstructor(); +}; + +class GenericProjectivityConstructor + : public MergeObjectConstructor +{ +public: + GenericProjectivityConstructor(); + ~GenericProjectivityConstructor(); +}; + +class InversionConstructor + : public MergeObjectConstructor +{ +public: + InversionConstructor(); + ~InversionConstructor(); +}; + +class GenericIntersectionConstructor + : public MergeObjectConstructor +{ +public: + GenericIntersectionConstructor(); + ~GenericIntersectionConstructor(); + + bool isIntersection() const; + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& v ) const; + QString selectStatement( + const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& w ) const; +}; + +class MidPointOfTwoPointsConstructor + : public StandardConstructorBase +{ + ArgsParser mparser; +public: + MidPointOfTwoPointsConstructor(); + ~MidPointOfTwoPointsConstructor(); + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, + KigWidget& w ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; +}; + +class TestConstructor + : public StandardConstructorBase +{ + const ArgsParserObjectType* mtype; +public: + TestConstructor( const ArgsParserObjectType* type, const char* descname, + const char* desc, const char* iconfile ); + ~TestConstructor(); + void drawprelim( const ObjectDrawer& drawer, KigPainter& p, const std::vector<ObjectCalcer*>& parents, + const KigDocument& ) const; + std::vector<ObjectHolder*> build( const std::vector<ObjectCalcer*>& os, KigDocument& d, + KigWidget& w ) const; + const int wantArgs( const std::vector<ObjectCalcer*>& os, + const KigDocument& d, const KigWidget& v ) const; + void plug( KigPart* doc, KigGUIAction* kact ); + bool isTransform() const; + bool isTest() const; + + BaseConstructMode* constructMode( KigPart& doc ); +}; + +class TangentConstructor + : public MergeObjectConstructor +{ +public: + TangentConstructor(); + ~TangentConstructor(); + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& v ) const; +// QString selectStatement( +// const std::vector<ObjectCalcer*>& sel, const KigDocument& d, +// const KigWidget& w ) const; +}; + +class CocConstructor + : public MergeObjectConstructor +{ +public: + CocConstructor(); + ~CocConstructor(); + + QString useText( const ObjectCalcer& o, const std::vector<ObjectCalcer*>& sel, const KigDocument& d, + const KigWidget& v ) const; +// QString selectStatement( +// const std::vector<ObjectCalcer*>& sel, const KigDocument& d, +// const KigWidget& w ) const; +}; + +bool relativePrimes( int n, int p ); +#endif |