// Copyright (C)  2004  Pino Toscano <toscano.pino@tiscali.it>
// Copyright (C)  2004  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 "drgeo-filter.h"

#include "drgeo-filter-chooser.h"
#include "filters-common.h"

#include "../kig/kig_document.h"
#include "../kig/kig_part.h"
#include "../misc/common.h"
#include "../misc/coordinate.h"
#include "../objects/angle_type.h"
#include "../objects/arc_type.h"
#include "../objects/bogus_imp.h"
#include "../objects/circle_imp.h"
#include "../objects/circle_type.h"
#include "../objects/conic_imp.h"
#include "../objects/conic_types.h"
#include "../objects/curve_imp.h"
#include "../objects/intersection_types.h"
#include "../objects/line_imp.h"
#include "../objects/line_type.h"
#include "../objects/object_calcer.h"
#include "../objects/object_drawer.h"
#include "../objects/object_factory.h"
#include "../objects/object_holder.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_type.h"
#include "../objects/transform_types.h"
#include "../objects/vector_type.h"

#include <math.h>

#include <tqfile.h>
#include <tqnamespace.h>

#include <tdelocale.h>

#undef DRGEO_DEBUG
//#define DRGEO_DEBUG

struct DrGeoHierarchyElement
{
  TQString id;
  std::vector<TQString> parents;
};

KigFilterDrgeo::KigFilterDrgeo()
{
}

KigFilterDrgeo::~KigFilterDrgeo()
{
}

bool KigFilterDrgeo::supportMime( const TQString& mime )
{
  return mime == "application/x-drgeo";
}

KigDocument* KigFilterDrgeo::load( const TQString& file )
{
  TQFile f( file );
  if ( ! f.open( IO_ReadOnly ) )
  {
    fileNotFound( file );
    return 0;
  }

  TQStringList figures;
  TQDomDocument doc( "drgenius" );
  if ( !doc.setContent( &f ) )
    KIG_FILTER_PARSE_ERROR;
  TQDomElement main = doc.documentElement();
  int nmacros = 0;
  // reading figures...
  for ( TQDomNode n = main.firstChild(); ! n.isNull(); n = n.nextSibling() )
  {
    TQDomElement e = n.toElement();
    if ( e.isNull() ) continue;
    else if ( e.tagName() == "drgeo" )
      figures.append( e.attribute( "name" ) );
    else if ( e.tagName() == "macro" )
      nmacros++;
  }
  if ( figures.isEmpty() ) {
    if( nmacros > 0 )
      warning( i18n( "The Dr. Geo file \"%1\" is a macro file so it contains no "
                     "figures." ).arg( file ) );
    else
      warning( i18n( "There are no figures in Dr. Geo file \"%1\"." ).arg( file ) );
    return 0;
  }

  int nfig = figures.count();
  // no figures, no party...
  if ( nfig == 0 )
    return 0;

  int myfig = 0;
  
  if ( nfig > 1 )
  {
    // Dr. Geo file has more than 1 figure, let the user choose one...
    KigFilterDrgeoChooser* c = new KigFilterDrgeoChooser( figures );
    myfig = c->exec();
    delete c;
  }

#ifdef DRGEO_DEBUG
  kdDebug() << "drgeo file " << file << endl;
#endif
  int curfig = -1;

  for ( TQDomNode n = main.firstChild(); ! n.isNull(); n = n.nextSibling() )
  {
    TQDomElement e = n.toElement();
    if ( e.isNull() ) continue;
    else if ( e.tagName() == "drgeo" )
    {
      curfig += 1;
      if ( curfig == myfig )
      {
#ifdef DRGEO_DEBUG
        kdDebug() << "- Figure: '" << e.attribute("name") << "'" << endl;
#endif
        bool grid = !e.attribute( "grid" ).isEmpty() &&
                    ( e.attribute( "grid" ) != "False" );
        return importFigure( e.firstChild(), file, grid );
      }
    }
  }

  return 0;
}

int convertDrgeoIndex( const std::vector<DrGeoHierarchyElement> es, const TQString myid )
{
  for ( uint i = 0; i < es.size(); ++i )
    if ( es[i].id == myid )
      return i;
  return -1;
}

const Coordinate convertDrgeoLineParam( const double param, const LineData& line )
{
  const double n = ( param - 0.5 ) * M_PI;
  const Coordinate c = line.dir() / line.dir().length();
  const Coordinate p = line.a + tan( n ) * c;
  return p;
}

const Coordinate convertDrgeoHalflineParam( const double param, const LineData& line )
{
  const double n = param * M_PI * 0.5;
  const Coordinate c = line.dir() / line.dir().length();
  const Coordinate p = line.a + tan( n ) * c;
  return p;
}

KigDocument* KigFilterDrgeo::importFigure( TQDomNode f, const TQString& file, const bool grid )
{
  KigDocument* ret = new KigDocument();

  using namespace std;
  std::vector<DrGeoHierarchyElement> elems;
  int withoutid = 0;

  // 1st: fetch relationships and build an appropriate structure
  for (TQDomNode a = f; ! a.isNull(); a = a.nextSibling() )
  {
    TQDomElement domelem = a.toElement();
    if ( domelem.isNull() ) continue;
    else
    {
      DrGeoHierarchyElement elem;
#ifdef DRGEO_DEBUG
      kdDebug() << "  * " << domelem.tagName() << "(" << domelem.attribute("type") << ")" << endl;
#endif
      for ( TQDomNode c = domelem.firstChild(); ! c.isNull(); c = c.nextSibling() )
      {
        TQDomElement ce = c.toElement();
        if ( ce.isNull() ) continue;
        else if ( ce.tagName() == "parent" )
          elem.parents.push_back( ce.attribute( "ref" ) );
      }
      TQString curid = domelem.attribute( "id" );
      elem.id = !curid.isNull() ? curid : TQString::number( withoutid++ ) ;
      elems.push_back( elem );
    }
  }

#ifdef DRGEO_DEBUG
  TQString x;
  kdDebug() << "+++ elems" << endl;
  for ( uint i = 0; i < elems.size(); ++i )
  {
    x = "";
    for ( uint j = 0; j < elems[i].parents.size(); ++j )
    {
      x += elems[i].parents[j] + "_";
    }
    kdDebug() << "  --> " << i << " - " << elems[i].id << " - " << x << endl;
  }
#endif

  // 2nd: let's draw!
  int curid = 0;
  const ObjectFactory* fact = ObjectFactory::instance();
  std::vector<ObjectHolder*> holders;
  std::vector<ObjectHolder*> holders2;
  ObjectTypeCalcer* oc = 0;
  ObjectCalcer* oc2 = 0;
  int nignored = 0;

  // there's no need to sort the objects because it seems that DrGeo objects
  // appear in the right order... so let's go!
  for (TQDomNode a = f; ! a.isNull(); a = a.nextSibling() )
  {
#ifdef DRGEO_DEBUG
    kdDebug() << "+++ id: " << curid << endl;
#endif
    const DrGeoHierarchyElement& el = elems[curid];
    std::vector<ObjectCalcer*> parents;
    for ( uint j = 0; j < el.parents.size(); ++j )
    {
      int parentid = convertDrgeoIndex( elems, el.parents[j] );
      if ( parentid == -1 )
        KIG_FILTER_PARSE_ERROR;
      parents.push_back( holders[parentid-nignored]->calcer() );
    };
    TQDomElement domelem = a.toElement();

#ifdef DRGEO_DEBUG
    if ( parents.size() > 0 )
      for ( uint j = 0; j < parents.size(); ++j )
      {
        kdDebug() << "+++++++++ parent[" << j << "]: " << parents[j] << " - "
                  << parents[j]->imp()->type()->internalName() << endl;
      }
    else
      kdDebug() << "+++++++++ parents: NO" << endl;
    kdDebug() << "+++++++++ " << domelem.tagName() << " - " << domelem.attribute("type") << endl;
#endif

    if ( domelem.isNull() ) continue;
    else if ( domelem.tagName() == "point" )
    {
      TQString xs;
      TQString ys;
      TQString values;
      for ( TQDomNode c = domelem.firstChild(); ! c.isNull(); c = c.nextSibling() )
      {
        TQDomElement ce = c.toElement();
        if ( ce.isNull() ) continue;
        else if ( ce.tagName() == "x" )
          xs = ce.text();
        else if ( ce.tagName() == "y" )
          ys = ce.text();
        else if ( ce.tagName() == "value" )
          values = ce.text();
      }
      if ( domelem.attribute( "type" ) == "Free" )
      {
        bool ok;
        bool ok2;
        double x = xs.toDouble( &ok );
        double y = ys.toDouble( &ok2 );
        if ( ! ( ok && ok2 ) )
          KIG_FILTER_PARSE_ERROR;
        oc = fact->fixedPointCalcer( Coordinate( x, y ) );
      }
      else if ( domelem.attribute( "type" ) == "Middle_2pts" )
        oc = new ObjectTypeCalcer( MidPointType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Middle_segment" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        if ( !parents[0]->imp()->inherits( SegmentImp::stype() ) )
          KIG_FILTER_PARSE_ERROR;
        ObjectPropertyCalcer* o1 = fact->propertyObjectCalcer( parents[0], "end-point-A" );
        o1->calc( *ret );
        ObjectPropertyCalcer* o2 = fact->propertyObjectCalcer( parents[0], "end-point-B" );
        o2->calc( *ret );
        std::vector<ObjectCalcer*> args;
        args.push_back( o1 );
        args.push_back( o2 );
        oc = new ObjectTypeCalcer( MidPointType::instance(), args );
      }
      else if ( domelem.attribute( "type" ) == "On_curve" )
      {
        bool ok3;
        double value = values.toDouble( &ok3 );
        if ( ! ok3 )
          KIG_FILTER_PARSE_ERROR;
        if ( ( parents[0]->imp()->inherits( CircleImp::stype() ) ) ||
             ( parents[0]->imp()->inherits( SegmentImp::stype() ) ) )
          oc = fact->constrainedPointCalcer( parents[0], value );
        else if ( parents[0]->imp()->inherits( LineImp::stype() ) )
        {
          const LineData l = static_cast<const LineImp*>( parents[0]->imp() )->data();
          const Coordinate p = convertDrgeoLineParam( value, l );
          oc = fact->constrainedPointCalcer( parents[0], p, *ret );
        }
        else if ( parents[0]->imp()->inherits( RayImp::stype() ) )
        {
          const LineData l = static_cast<const RayImp*>( parents[0]->imp() )->data();
          const Coordinate p = convertDrgeoHalflineParam( value, l );
          oc = fact->constrainedPointCalcer( parents[0], p, *ret );
        }
        else if ( parents[0]->imp()->inherits( ArcImp::stype() ) )
          oc = fact->constrainedPointCalcer( parents[0], 1 - value );
        else
        {
//          oc = fact->constrainedPointCalcer( parents[0], value );
          notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                    "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                      domelem.attribute( "type" ) ) );
          return 0;
        }
      }
      else if ( domelem.attribute( "type" ) == "Intersection" )
      {
        if ( ( parents[0]->imp()->inherits( AbstractLineImp::stype() ) ) &&
             ( parents[1]->imp()->inherits( AbstractLineImp::stype() ) ) )
          oc = new ObjectTypeCalcer( LineLineIntersectionType::instance(), parents );
        else
        {
          bool ok;
          int which = domelem.attribute( "extra" ).toInt( &ok );
          if ( !ok ) KIG_FILTER_PARSE_ERROR;
          if ( which == 1 ) which = -1;
          else if ( which == 0 ) which = 1;
          else KIG_FILTER_PARSE_ERROR;
          std::vector<ObjectCalcer*> args = parents;
          const ObjectType* type = 0;
          args.push_back( new ObjectConstCalcer( new IntImp( which ) ) );
          if ( ( parents[0]->imp()->inherits( CircleImp::stype() ) ) &&
               ( parents[1]->imp()->inherits( CircleImp::stype() ) ) )
            type = CircleCircleIntersectionType::instance();
          else if ( ( parents[0]->imp()->inherits( CircleImp::stype() ) &&
                      parents[1]->imp()->inherits( AbstractLineImp::stype() ) ) ||
                    ( parents[1]->imp()->inherits( CircleImp::stype() ) &&
                      parents[0]->imp()->inherits( AbstractLineImp::stype() ) ) )
            type = ConicLineIntersectionType::instance();
          else if ( ( parents[0]->imp()->inherits( ArcImp::stype() ) &&
                      parents[1]->imp()->inherits( AbstractLineImp::stype() ) ) ||
                    ( parents[1]->imp()->inherits( ArcImp::stype() ) &&
                      parents[0]->imp()->inherits( AbstractLineImp::stype() ) ) )
            type = ArcLineIntersectionType::instance();
          else
          {
            notSupported( file, i18n( "This Dr. Geo file contains an intersection type, "
                                      "which Kig does not currently support." ) );
            return 0;
          }
          oc = new ObjectTypeCalcer( type, args );
        }
      }
      else if ( domelem.attribute( "type" ) == "Reflexion" )
        oc = new ObjectTypeCalcer( LineReflectionType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Symmetry" )
        oc = new ObjectTypeCalcer( PointReflectionType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Translation" )
        oc = new ObjectTypeCalcer( TranslatedType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Rotation" )
        oc = new ObjectTypeCalcer( RotationType::instance(), parents );
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                    domelem.attribute( "type" ) ) );
        return 0;
      }
#ifdef DRGEO_DEBUG
      kdDebug() << "+++++++++ oc:" << oc << endl;
#endif
    }
    else if( ( domelem.tagName() == "line" ) ||
             ( domelem.tagName() == "halfLine" ) ||
             ( domelem.tagName() == "segment" ) ||
             ( domelem.tagName() == "vector" ) ||
             ( domelem.tagName() == "circle" ) ||
             ( domelem.tagName() == "arcCircle" ) ||
             ( domelem.tagName() == "polygon" ) )
    {
      const ObjectType* type = 0;
      if ( domelem.attribute( "type" ) == "2pts" )
      {
        if( domelem.tagName() == "line" )
          type = LineABType::instance();
        else if( domelem.tagName() == "halfLine" )
          type = RayABType::instance();
        else if( domelem.tagName() == "segment" )
          type = SegmentABType::instance();
        else if( domelem.tagName() == "vector" )
          type = VectorType::instance();
        else if( domelem.tagName() == "circle" )
          type = CircleBCPType::instance();
        else
        {
          notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                    "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                    domelem.attribute( "type" ) ) );
          return 0;
        }
        oc = new ObjectTypeCalcer( type, parents );
      }
      else if( domelem.attribute( "type" ) == "3pts" )
      {
        if( domelem.tagName() == "arcCircle" )
          type = ArcBTPType::instance();
        else
        {
          notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                    "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                    domelem.attribute( "type" ) ) );
          return 0;
        }
        oc = new ObjectTypeCalcer( type, parents );
      }
      else if( domelem.attribute( "type" ) == "segment" )
      {
        if( domelem.tagName() == "circle" )
        {
          type = CircleBPRType::instance();
          ObjectPropertyCalcer* o = fact->propertyObjectCalcer( parents[1], "length" );
          o->calc( *ret );
          ObjectCalcer* a = parents[0];
          parents.clear();
          parents.push_back( a );
          parents.push_back( o );
        }
        else
        {
          notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                    "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                    domelem.attribute( "type" ) ) );
          return 0;
        }
        oc = new ObjectTypeCalcer( type, parents );
      }
      else if( domelem.attribute( "type" ) == "npts" )
      {
        if( domelem.tagName() == "polygon" )
        {
          if ( parents.size() < 3 ) KIG_FILTER_PARSE_ERROR;
          type = PolygonBNPType::instance();
        }
        else
        {
          notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                    "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                    domelem.attribute( "type" ) ) );
          return 0;
        }
        oc = new ObjectTypeCalcer( type, parents );
      }
      else if ( domelem.attribute( "type" ) == "perpendicular" )
        oc = new ObjectTypeCalcer( LinePerpendLPType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "parallel" )
        oc = new ObjectTypeCalcer( LineParallelLPType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Reflexion" )
        oc = new ObjectTypeCalcer( LineReflectionType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Symmetry" )
        oc = new ObjectTypeCalcer( PointReflectionType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Translation" )
        oc = new ObjectTypeCalcer( TranslatedType::instance(), parents );
      else if ( domelem.attribute( "type" ) == "Rotation" )
        oc = new ObjectTypeCalcer( RotationType::instance(), parents );
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                  domelem.attribute( "type" ) ) );
        return 0;
      }
#ifdef DRGEO_DEBUG
      kdDebug() << "+++++++++ oc:" << oc << endl;
#endif
    }
    else if( ( domelem.tagName() == "numeric" ) ||
             ( domelem.tagName() == "equation" ) )
    {
      TQString xs;
      TQString ys;
      TQString value;
      for ( TQDomNode c = domelem.firstChild(); ! c.isNull(); c = c.nextSibling() )
      {
        TQDomElement ce = c.toElement();
        if ( ce.isNull() ) continue;
        else if ( ce.tagName() == "x" )
          xs = ce.text();
        else if ( ce.tagName() == "y" )
          ys = ce.text();
        else if ( ce.tagName() == "value" )
          value = ce.text();
      }
      bool ok;
      bool ok2;
      double x = xs.toDouble( &ok );
      double y = ys.toDouble( &ok2 );
      if ( ! ( ok && ok2 ) )
        KIG_FILTER_PARSE_ERROR;
      Coordinate m( x, y );
      // types of 'numeric'
      // ugly hack to show value numerics...
      if ( domelem.attribute( "type" ) == "value" )
      {
        bool ok3;
        double dvalue = value.toDouble( &ok3 );
        if ( ok3 )
          value = TQString( "%1" ).arg( dvalue, 0, 'g', 3 );
        oc = fact->labelCalcer( value, m, false, std::vector<ObjectCalcer*>(), *ret );
      }
      else if ( domelem.attribute( "type" ) == "pt_abscissa" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "coordinate-x", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "pt_ordinate" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "coordinate-y", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "segment_length" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "length", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "circle_perimeter" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "circumference", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "arc_length" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "arc-length", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "distance_2pts" )
      {
        if ( parents.size() != 2 ) KIG_FILTER_PARSE_ERROR;
        ObjectTypeCalcer* so = new ObjectTypeCalcer( SegmentABType::instance(), parents );
        so->calc( *ret );
        oc = filtersConstructTextObject( m, so, "length", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "vector_norm" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "length", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "vector_abscissa" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "length-x", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "vector_ordinate" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "length-y", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "slope" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "slope", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "distance_pt_line" )
      {
        if ( parents.size() != 2 ) KIG_FILTER_PARSE_ERROR;
        std::vector<ObjectCalcer*> args;
        args.push_back( parents[1] );
        args.push_back( parents[0] );
        ObjectTypeCalcer* po = new ObjectTypeCalcer( LinePerpendLPType::instance(), args );
        po->calc( *ret );
        args.clear();
        args.push_back( parents[1] );
        args.push_back( po );
        ObjectTypeCalcer* io = new ObjectTypeCalcer( LineLineIntersectionType::instance(), args );
        io->calc( *ret );
        args.clear();
        args.push_back( parents[0] );
        args.push_back( io );
        ObjectTypeCalcer* so = new ObjectTypeCalcer( SegmentABType::instance(), args );
        so->calc( *ret );
        oc = filtersConstructTextObject( m, so, "length", *ret, false );
      }
      // types of 'equation'
      else if ( domelem.attribute( "type" ) == "line" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "equation", *ret, false );
      }
      else if ( domelem.attribute( "type" ) == "circle" )
      {
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        oc = filtersConstructTextObject( m, parents[0], "simply-cartesian-equation", *ret, false );
      }
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                  domelem.attribute( "type" ) ) );
        return 0;
      }
#ifdef DRGEO_DEBUG
      kdDebug() << "+++++++++ oc:" << oc << endl;
#endif
    }
    else if ( domelem.tagName() == "angle" )
    {
      if ( domelem.attribute( "type" ) == "3pts" )
      {
        if ( parents.size() != 3 ) KIG_FILTER_PARSE_ERROR;
        oc = new ObjectTypeCalcer( HalfAngleType::instance(), parents );
      }
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                  domelem.attribute( "type" ) ) );
        return 0;
      }
#ifdef DRGEO_DEBUG
      kdDebug() << "+++++++++ oc:" << oc << endl;
#endif
    }
    else if ( domelem.tagName() == "script" )
    {
      TQString xs;
      TQString ys;
      TQString text;
      for ( TQDomNode c = domelem.firstChild(); ! c.isNull(); c = c.nextSibling() )
      {
        TQDomElement ce = c.toElement();
        if ( ce.isNull() ) continue;
        else if ( ce.tagName() == "x" )
          xs = ce.text();
        else if ( ce.tagName() == "y" )
          ys = ce.text();
        else if ( ce.tagName() == "code" )
          text = ce.text();
      }
      bool ok;
      bool ok2;
      double x = xs.toDouble( &ok );
      double y = ys.toDouble( &ok2 );
      if ( ! ( ok && ok2 ) )
        KIG_FILTER_PARSE_ERROR;
      // since Kig doesn't support Guile scripts, it will write script's text
      // in a label, so the user can freely see the code and make whatever
      // he/she wants
      // possible idea: construct a new script object with the parents of Guile
      // one and the Guile code inserted commented... depends on a better
      // handling of arguments in scripts?
      if ( domelem.attribute( "type" ) == "nitems" )
        oc = fact->labelCalcer( text, Coordinate( x, y ), false, std::vector<ObjectCalcer*>(), *ret );
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                  domelem.attribute( "type" ) ) );
        return 0;
      }
    }
    else if ( domelem.tagName() == "locus" )
    {
      if ( domelem.attribute( "type" ) == "None" )
        oc = fact->locusCalcer( parents[0], parents[1] );
      else
      {
        notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                  "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                  domelem.attribute( "type" ) ) );
        return 0;
      }
#ifdef DRGEO_DEBUG
      kdDebug() << "+++++++++ oc:" << oc << endl;
#endif
    }
    else if ( ( domelem.tagName() == "boundingBox" ) ||
              ( domelem.tagName() == "customUI" ) )
    {
      // ignoring these elements, since they are not useful to us...
      nignored++;
    }
    else
    {
#ifdef DRGEO_DEBUG
      kdDebug() << ">>>>>>>>> UNKNOWN OBJECT" << endl;
#endif
      notSupported( file, i18n( "This Dr. Geo file contains a \"%1 %2\" object, "
                                "which Kig does not currently support." ).arg( domelem.tagName() ).arg(
                                domelem.attribute( "type" ) ) );
      return 0;
    }
    curid++;
    if ( oc == 0 )
      continue;

// reading color
    TQColor co( domelem.attribute( "color" ) );
    if ( ! co.isValid() )
      if ( domelem.attribute( "color" ) == "Bordeaux" )
        co.setRgb( 145, 0, 0 );
      else
        co = TQt::blue;
// reading width and style
// Dashed -> the little one
// Normal -> the medium
// Thick  -> the biggest one
    int w = -1;
    Qt::PenStyle s = Qt::SolidLine;
    if ( domelem.tagName() == "point" )
    {
      if ( domelem.attribute( "thickness" ) == "Normal" )
        w = 7;
      else if ( domelem.attribute( "thickness" ) == "Thick" )
        w = 9;
    }
    else
    {
      if ( domelem.attribute( "thickness" ) == "Dashed" )
        s = Qt::DotLine;
      if ( domelem.attribute( "thickness" ) == "Thick" )
        w = 2;
    }
    TQString ps = domelem.attribute( "style" );
    int pointstyle = ObjectDrawer::pointStyleFromString( ps );
// show this object?
    bool show = ( ( domelem.attribute( "masked" ) != "True" ) &&
                  ( domelem.attribute( "masked" ) != "Alway" ) );
// costructing the ObjectDrawer*
    ObjectDrawer* d = new ObjectDrawer( co, w, show, s, pointstyle );
// reading object name
    TQString strname = domelem.attribute( "name" );
    ObjectConstCalcer* name = new ObjectConstCalcer( new StringImp( strname ) );

// creating the ObjectHolder*
    ObjectHolder* o = new ObjectHolder( oc, d, name );
    holders.push_back( o );
// calc()
#ifdef DRGEO_DEBUG
    kdDebug() << ">>>>>>>>> calc" << endl;
#endif
    holders[curid-1-nignored]->calc( *ret );

    if ( domelem.tagName() == "point" )
    {
      if ( !strname.isEmpty() )
      {
        std::vector<ObjectCalcer*> args2;
        args2.push_back( o->nameCalcer() );
        oc2 = fact->attachedLabelCalcer( TQString::fromLatin1( "%1" ), oc,
                 static_cast<const PointImp*>( oc->imp() )->coordinate(),
                 false, args2, *ret );
        co = TQt::black;
      }
    }
    else if ( domelem.tagName() == "angle" )
    {
      oc2 = filtersConstructTextObject(
               static_cast<const PointImp*>( holders[curid-1-nignored]->calcer()->parents()[1]->imp() )->coordinate(),
               holders[curid-1-nignored]->calcer(), "angle-degrees", *ret, false );
    }

    oc = 0;

    if ( oc2 != 0 )
    {
      oc2->calc( *ret );
      ObjectDrawer* d2 = new ObjectDrawer( co );
      ObjectHolder* o2 = new ObjectHolder( oc2, d2 );
      holders2.push_back( o2 );
      oc2 = 0;
    }
  }

  ret->addObjects( holders );
  ret->addObjects( holders2 );
  ret->setGrid( grid );
  ret->setAxes( grid );
  return ret;
}

KigFilterDrgeo* KigFilterDrgeo::instance()
{
  static KigFilterDrgeo f;
  return &f;
}
