// Copyright (C) 2002 Dominique Devriese // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301, USA. #include "object_factory.h" #include "bogus_imp.h" #include "curve_imp.h" #include "intersection_types.h" #include "line_imp.h" #include "object_drawer.h" #include "object_holder.h" #include "other_type.h" #include "point_imp.h" #include "point_type.h" #include "text_type.h" #include "conic_types.h" #include "../kig/kig_document.h" #include "../kig/kig_view.h" #include "../misc/calcpaths.h" #include "../misc/coordinate.h" #include "../misc/object_hierarchy.h" #include "../misc/special_constructors.h" #include #include #include ObjectHolder* ObjectFactory::fixedPoint( const Coordinate& c ) const { ObjectHolder* o = new ObjectHolder( fixedPointCalcer( c ) ); return o; } ObjectTypeCalcer* ObjectFactory::fixedPointCalcer( const Coordinate& c ) const { std::vector args; args.push_back( new ObjectConstCalcer( new DoubleImp( c.x ) ) ); args.push_back( new ObjectConstCalcer( new DoubleImp( c.y ) ) ); ObjectTypeCalcer* oc = new ObjectTypeCalcer( FixedPointType::instance(), args ); return oc; } ObjectHolder* ObjectFactory::numericValue( const double value, const Coordinate& loc, const KigDocument& doc ) const { return new ObjectHolder( numericValueCalcer( value, loc, doc ) ); } ObjectTypeCalcer* ObjectFactory::numericValueCalcer( const double value, const Coordinate& loc, const KigDocument& doc ) const { std::vector parents; parents.reserve( 4 ); const bool needframe = false; parents.push_back( new ObjectConstCalcer( new IntImp( needframe ? 1 : 0 ) ) ); parents.push_back( getAttachPoint( 0, loc, doc ) ); parents.push_back( new ObjectConstCalcer( new StringImp( "%1" ) ) ); parents.push_back( new ObjectConstCalcer( new DoubleImp( value ) ) ); ObjectTypeCalcer* ret = new ObjectTypeCalcer( NumericTextType::instance(), parents ); ret->calc( doc ); return ret; } ObjectTypeCalcer* ObjectFactory::cursorPointCalcer( const Coordinate& c ) const { std::vector args; args.push_back( new ObjectConstCalcer( new DoubleImp( c.x ) ) ); args.push_back( new ObjectConstCalcer( new DoubleImp( c.y ) ) ); ObjectTypeCalcer* oc = new ObjectTypeCalcer( CursorPointType::instance(), args ); return oc; } const ObjectFactory* ObjectFactory::instance() { static ObjectFactory f; return &f; } ObjectTypeCalcer* ObjectFactory::sensiblePointCalcer( const Coordinate& c, const KigDocument& d, const KigWidget& w ) const { std::vector os = d.whatAmIOn( c, w ); if ( os.size() == 2 ) { // we can calc intersection point *only* between two objects... std::vector args; int linecount = 0; int coniccount = 0; int circlecount = 0; // Note: a circle is a conic // int cubiccount = 0; // not yet implemented int lineid = -1; int conicid = -1; // int cubicid = -1; for (int i = 0; i < 2; i++) { if ( os[i]->imp()->inherits( AbstractLineImp::stype() ) ) {linecount++; lineid = i;} if ( os[i]->imp()->inherits( ConicImp::stype() ) ) {coniccount++; conicid = i;} if ( os[i]->imp()->inherits( CircleImp::stype() ) ) circlecount++; // if ( os[i]->imp()->inherits( CubicImp::stype() ) ) {cubiccount++; cubicid = i;} } if ( linecount == 2 ) { // the simplest case: two lines... args.push_back( os[0]->calcer() ); args.push_back( os[1]->calcer() ); return new ObjectTypeCalcer( LineLineIntersectionType::instance(), args ); } if ( circlecount == 2 || ( coniccount == 1 && linecount == 1 ) ) { // in this case there are generally two intersections, we need // to check if one of these is already present, in which case // we must use the "Other" variant of the intersection types std::vector points = d.findIntersectionPoints( os[0]->calcer(), os[1]->calcer() ); std::vector uniquepoints = removeDuplicatedPoints( points ); if ( uniquepoints.size() == 1 ) { bool doother = true; std::vector parents = uniquepoints[0]->parents(); if ( parents.size() == 3 ) { if ( ( parents[0] == os[0]->calcer() && parents[1] == os[1]->calcer() ) || ( parents[0] == os[1]->calcer() && parents[1] == os[0]->calcer() ) ) { if ( parents[2]->imp()->inherits( IntImp::stype() ) ) doother = false; // we should test if the type is // ConicLineIntersectionType or // CircleCircleIntersectionType } } if ( doother ) { // in this case we construct an OtherIntersection printf ("*** trovata altra intersezione!\n"); if ( circlecount == 2 ) { args.push_back( os[0]->calcer() ); args.push_back( os[1]->calcer() ); args.push_back( uniquepoints[0] ); return new ObjectTypeCalcer( CircleCircleOtherIntersectionType::instance(), args ); } else { args.push_back( os[conicid]->calcer() ); args.push_back( os[lineid]->calcer() ); args.push_back( uniquepoints[0] ); return new ObjectTypeCalcer( ConicLineOtherIntersectionType::instance(), args ); } } } } if ( coniccount == 1 && linecount == 1 ) { // conic-line intersection... const ConicImp* conic = static_cast( os[conicid]->imp() ); const AbstractLineImp* line = static_cast( os[lineid]->imp() ); // we have two intersections, select the nearest one Coordinate p1, p2; if ( circlecount ) // in this case we cannot use the ConicLine computation { // because "which" behaves differently when "calc"-ing const CircleImp* c = static_cast( conic ); p1 = calcCircleLineIntersect( c->center(), c->squareRadius(), line->data(), -1 ); p2 = calcCircleLineIntersect( c->center(), c->squareRadius(), line->data(), 1 ); } else { p1 = calcConicLineIntersect( conic->cartesianData(), line->data(), 0.0, -1 ); p2 = calcConicLineIntersect( conic->cartesianData(), line->data(), 0.0, 1 ); } int which = -1; if ( (p2-c).length() < (p1-c).length() ) which = 1; args.push_back( os[conicid]->calcer() ); args.push_back( os[lineid]->calcer() ); args.push_back( new ObjectConstCalcer( new IntImp( which ) ) ); return new ObjectTypeCalcer( ConicLineIntersectionType::instance(), args ); } if ( circlecount == 2 ) { // circle-circle intersection... const CircleImp* c1 = static_cast( os[0]->imp() ); const CircleImp* c2 = static_cast( os[1]->imp() ); const Coordinate o1 = c1->center(); const Coordinate o2 = c2->center(); const double r1sq = c1->squareRadius(); const Coordinate a = calcCircleRadicalStartPoint( o1, o2, r1sq, c2->squareRadius() ); const LineData lined = LineData (a, Coordinate ( a.x -o2.y + o1.y, a.y + o2.x - o1.x )); Coordinate p1 = calcCircleLineIntersect( o1, r1sq, lined, -1 ); Coordinate p2 = calcCircleLineIntersect( o1, r1sq, lined, 1 ); int which = -1; if ( (p2-c).length() < (p1-c).length() ) which = 1; args.push_back( os[0]->calcer() ); args.push_back( os[1]->calcer() ); args.push_back( new ObjectConstCalcer( new IntImp( which ) ) ); return new ObjectTypeCalcer( CircleCircleIntersectionType::instance(), args ); } if ( coniccount == 2 ) { // conic-conic intersection... const ConicImp* conic1 = static_cast( os[0]->imp() ); const ConicImp* conic2 = static_cast( os[1]->imp() ); bool valid; double d1, d2, d3, d4; Coordinate p1, p2, p3, p4; d1 = d2 = d3 = d4 = std::numeric_limits::max(); const LineData l1 = calcConicRadical( static_cast( conic1 )->cartesianData(), static_cast( conic2 )->cartesianData(), -1, 1, valid); if ( valid ) { p1 = calcConicLineIntersect( conic1->cartesianData(), l1, 0.0, -1 ); p2 = calcConicLineIntersect( conic1->cartesianData(), l1, 0.0, 1 ); d1 = (p1-c).length(); d2 = (p2-c).length(); } const LineData l2 = calcConicRadical( static_cast( conic1 )->cartesianData(), static_cast( conic2 )->cartesianData(), 1, 1, valid); if ( valid ) { p3 = calcConicLineIntersect( conic1->cartesianData(), l2, 0.0, -1 ); p4 = calcConicLineIntersect( conic1->cartesianData(), l2, 0.0, 1 ); d3 = (p3-c).length(); d4 = (p4-c).length(); } double d12 = fmin( d1, d2 ); double d34 = fmin( d3, d4 ); // test which is the right point, by now just choose p1 int whichline = -1; if ( d34 < d12 ) { whichline = 1; d1 = d3; d2 = d4; } int whichpoint = -1; if ( d2 < d1 ) whichpoint = 1; args.push_back( os[0]->calcer() ); args.push_back( os[1]->calcer() ); args.push_back( new ObjectConstCalcer( new IntImp( whichline ) ) ); args.push_back( new ObjectConstCalcer( new IntImp( 1 ) ) ); ObjectTypeCalcer* radical = new ObjectTypeCalcer( ConicRadicalType::instance(), args ); radical->calc( d ); args.clear(); args.push_back( os[0]->calcer() ); args.push_back( radical ); args.push_back( new ObjectConstCalcer( new IntImp( whichpoint ) ) ); return new ObjectTypeCalcer( ConicLineIntersectionType::instance(), args ); } // other cases will follow... } for ( std::vector::iterator i = os.begin(); i != os.end(); ++i ) if ( (*i)->imp()->inherits( CurveImp::stype() ) ) return constrainedPointCalcer( (*i)->calcer(), c, d ); return fixedPointCalcer( c ); } ObjectHolder* ObjectFactory::sensiblePoint( const Coordinate& c, const KigDocument& d, const KigWidget& w ) const { return new ObjectHolder( sensiblePointCalcer( c, d, w ) ); } ObjectTypeCalcer* ObjectFactory::relativePointCalcer( ObjectCalcer* o, const Coordinate& loc ) const { Coordinate reference = static_cast( o->imp() )->attachPoint(); assert( reference.valid() ); double x = 0.0; double y = 0.0; if ( loc.valid() ) { x = loc.x - reference.x; y = loc.y - reference.y; } std::vector parents; parents.push_back( new ObjectConstCalcer( new DoubleImp( x ) ) ); parents.push_back( new ObjectConstCalcer( new DoubleImp( y ) ) ); parents.push_back( o ); return new ObjectTypeCalcer( RelativePointType::instance(), parents ); } ObjectTypeCalcer* ObjectFactory::constrainedPointCalcer( ObjectCalcer* curve, double param ) const { assert( curve->imp()->inherits( CurveImp::stype() ) ); std::vector parents; parents.push_back( new ObjectConstCalcer( new DoubleImp( param ) ) ); parents.push_back( curve ); return new ObjectTypeCalcer( ConstrainedPointType::instance(), parents ); } ObjectHolder* ObjectFactory::constrainedPoint( ObjectCalcer* curve, double param ) const { return new ObjectHolder( constrainedPointCalcer( curve, param ) ); } ObjectTypeCalcer* ObjectFactory::constrainedPointCalcer( ObjectCalcer* curve, const Coordinate& c, const KigDocument& d ) const { assert( curve->imp()->inherits( CurveImp::stype() ) ); double param = static_cast( curve->imp() )->getParam( c, d ); return constrainedPointCalcer( curve, param ); } ObjectHolder* ObjectFactory::constrainedPoint( ObjectCalcer* curve, const Coordinate& c, const KigDocument& d ) const { return new ObjectHolder( constrainedPointCalcer( curve, c, d ) ); } ObjectTypeCalcer* ObjectFactory::constrainedRelativePointCalcer( ObjectCalcer* curve, double param ) const { assert( curve->imp()->inherits( CurveImp::stype() ) ); std::vector parents; parents.push_back( new ObjectConstCalcer( new DoubleImp( 0.0 ) ) ); parents.push_back( new ObjectConstCalcer( new DoubleImp( 0.0 ) ) ); parents.push_back( new ObjectConstCalcer( new DoubleImp( param ) ) ); parents.push_back( curve ); return new ObjectTypeCalcer( ConstrainedRelativePointType::instance(), parents ); } ObjectTypeCalcer* ObjectFactory::locusCalcer( ObjectCalcer* a, ObjectCalcer* b ) const { assert( dynamic_cast( a ) ); ObjectTypeCalcer* constrained = static_cast( a ); assert( constrained->type()->inherits( ObjectType::ID_ConstrainedPointType ) ); assert( constrained->parents().size() == 2 ); ObjectCalcer* curve = a->parents().back(); const ObjectCalcer* moving = b; std::vector hierparents; hierparents.push_back( constrained ); std::vector sideOfTree = sideOfTreePath( hierparents, moving ); std::copy( sideOfTree.begin(), sideOfTree.end(), std::back_inserter( hierparents ) ); ObjectHierarchy hier( hierparents, moving ); std::vector realparents( 2 + sideOfTree.size(), 0 ); realparents[0] = new ObjectConstCalcer( new HierarchyImp( hier ) ); realparents[1] = curve; std::copy( sideOfTree.begin(), sideOfTree.end(), realparents.begin() + 2 ); return new ObjectTypeCalcer( LocusType::instance(), realparents ); } ObjectHolder* ObjectFactory::locus( ObjectCalcer* a, ObjectCalcer* b ) const { return new ObjectHolder( locusCalcer( a, b ) ); } ObjectHolder* ObjectFactory::label( const QString& s, const Coordinate& loc, bool needframe, const std::vector& parents, const KigDocument& doc ) const { return new ObjectHolder( labelCalcer( s, loc, needframe, parents, doc ) ); } ObjectTypeCalcer* ObjectFactory::labelCalcer( const QString& s, const Coordinate& loc, bool needframe, const std::vector& parents, const KigDocument& doc ) const { return attachedLabelCalcer( s, 0, loc, needframe, parents, doc ); } ObjectTypeCalcer* ObjectFactory::attachedLabelCalcer( const QString& s, ObjectCalcer* p, const Coordinate& loc, bool needframe, const std::vector& nparents, const KigDocument& doc ) const { std::vector parents; parents.reserve( nparents.size() + 3 ); parents.push_back( new ObjectConstCalcer( new IntImp( needframe ? 1 : 0 ) ) ); parents.push_back( getAttachPoint( p, loc, doc ) ); parents.push_back( new ObjectConstCalcer( new StringImp( s ) ) ); std::copy( nparents.begin(), nparents.end(), std::back_inserter( parents ) ); ObjectTypeCalcer* ret = new ObjectTypeCalcer( TextType::instance(), parents ); ret->calc( doc ); return ret; } ObjectCalcer* ObjectFactory::getAttachPoint( ObjectCalcer* p, const Coordinate& loc, const KigDocument& doc ) const { /* * mp: (changes to add relative-attachment). Now an object is tested * as follows: * - if attachPoint() returns a valid coordinate, then we use the new method * - if it is a point: 'old-style' treatment (we can change this shortly) * - if it is a curve: use the new (nov 2009) ConstrainedRelativePoint * similar to the RelativePoint * * the first condition that matches determines the behaviour. * the new method works similarly to the curve case, but we generate a new * RelativePointType instead of a ConstrainedPointType; this will in turn make use * of the new attachPoint() method for objects. * * changed the preference order 2005/01/21 (now attachPoint has preference over points) * * NOTE: changes in the tests performed should be matched also in * modes/popup.cc (addNameLabel) and in label.cc (TextLabelModeBase::mouseMoved) */ if ( p && p->imp()->attachPoint().valid() ) { ObjectCalcer* o = relativePointCalcer( p, loc ); o->calc( doc ); return o; } else if ( p && p->imp()->inherits( PointImp::stype() ) ) { return p; } else if ( p && p->imp()->inherits( CurveImp::stype() ) ) { double param = 0.5; if ( loc.valid() ) param = static_cast( p->imp() )->getParam( loc, doc ); ObjectCalcer* o = constrainedRelativePointCalcer( p, param ); o->calc( doc ); return o; } else { if ( loc.valid() ) return new ObjectConstCalcer( new PointImp( loc ) ); else return new ObjectConstCalcer( new PointImp( Coordinate( 0, 0 ) ) ); } } ObjectHolder* ObjectFactory::attachedLabel( const QString& s, ObjectCalcer* locationparent, const Coordinate& loc, bool needframe, const std::vector& parents, const KigDocument& doc ) const { return new ObjectHolder( attachedLabelCalcer( s, locationparent, loc, needframe, parents, doc ) ); } ObjectPropertyCalcer* ObjectFactory::propertyObjectCalcer( ObjectCalcer* o, const char* p ) const { int wp = o->imp()->propertiesInternalNames().indexOf( p ); if ( wp == -1 ) return 0; return new ObjectPropertyCalcer( o, p ); } ObjectHolder* ObjectFactory::propertyObject( ObjectCalcer* o, const char* p ) const { return new ObjectHolder( propertyObjectCalcer( o, p ) ); } void ObjectFactory::redefinePoint( ObjectTypeCalcer* point, const Coordinate& c, KigDocument& doc, const KigWidget& w ) const { std::vector hos = doc.whatAmIOn( c, w ); std::vector os; ObjectCalcer* (ObjectHolder::*calcmeth)() = &ObjectHolder::calcer; std::transform( hos.begin(), hos.end(), std::back_inserter( os ), std::mem_fun( calcmeth ) ); ObjectCalcer* v = 0; // we don't want one of our children as a parent... std::set children = getAllChildren( point ); for ( std::vector::iterator i = os.begin(); i != os.end(); ++i ) if ( (*i)->imp()->inherits( CurveImp::stype() ) && children.find( *i ) == children.end() ) { v = *i; break; }; if ( v ) { // we want a constrained point... const CurveImp* curveimp = static_cast( v->imp() ); double newparam = curveimp->getParam( c, doc ); if ( point->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) { // point already was constrained -> simply update the param // DataObject and make sure point is on the right curve... ObjectCalcer* dataobj = 0; std::vector parents = point->parents(); assert( parents.size() == 2 ); assert ( parents[0]->imp()->inherits( DoubleImp::stype() ) ); dataobj = parents[0]; parents.clear(); parents.push_back( dataobj ); parents.push_back( v ); point->setParents( parents ); assert( dynamic_cast( dataobj ) ); static_cast( dataobj )->setImp( new DoubleImp( newparam ) ); } else { // point used to be fixed -> add a new DataObject etc. std::vector args; args.push_back( new ObjectConstCalcer( new DoubleImp( newparam ) ) ); args.push_back( v ); point->setType( ConstrainedPointType::instance() ); point->setParents( args ); } } else { // a fixed point... if ( point->type()->inherits( ObjectType::ID_ConstrainedPointType ) ) { // point used to be constrained.. std::vector a; a.push_back( new ObjectConstCalcer( new DoubleImp( c.x ) ) ); a.push_back( new ObjectConstCalcer( new DoubleImp( c.y ) ) ); point->setType( FixedPointType::instance() ); point->setParents( a ); } else { // point used to be fixed -> simply update the DataObject's // we can use the point's move function for that.. point->move( c, doc ); }; } }