/**
 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 "popup.h"
#include "popup.moc"

#include "../kig/kig_part.h"
#include "../kig/kig_document.h"
#include "../kig/kig_view.h"
#include "../kig/kig_commands.h"
#include "../objects/object_imp.h"
#include "../objects/object_drawer.h"
#include "../objects/bogus_imp.h"
#include "../objects/point_imp.h"
#include "../objects/line_imp.h"
#include "../objects/other_type.h"
#include "../objects/object_factory.h"
#include "../objects/polygon_imp.h"
#include "../objects/text_imp.h"
#include "../misc/lists.h"
#include "../misc/argsparser.h"
#include "../misc/kigpainter.h"
#include "../misc/coordinate_system.h"
#include "../misc/object_constructor.h"
#include "construct_mode.h"
#include "normal.h"
#include "moving.h"

#include <algorithm>
#include <functional>

#include <tqcursor.h>
#include <tqdialog.h>
#include <tqpen.h>
#include <tqregexp.h>
#include <tqvalidator.h>

#include <tdeaction.h>
#include <kcolordialog.h>
#include <tdeglobal.h>
#include <kiconloader.h>
#include <tdelocale.h>
#if KDE_IS_VERSION( 3, 1, 90 )
#include <kinputdialog.h>
#else
#include <klineeditdlg.h>
#endif

#include <config.h>

using namespace std;

class NormalModePopupObjects;

/**
 * This class is an abstract class.  Its role is to fill up the
 * NormalModePopupObjects with useful actions..
 */
class PopupActionProvider
{
public:
  virtual ~PopupActionProvider();
  /**
   * add all your entries to menu menu in popup popup.  Set nextfree
   * to the next free index..
   */
  virtual void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree ) = 0;
  /**
   * try to execute the id'th action you added to menu menu in popup
   * popup ( first is 0 ).  Return true if this action does indeed
   * belong to you ( is not greater than the number of actions you
   * added ).  Else return false, and subtract the number of actions
   * you added from id.  This requires you to keep a record of how
   * much actions you added ( unless it's a fixed number, of course
   * ).
   */
  virtual bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                              NormalModePopupObjects& popup,
                              KigPart& doc, KigWidget& w, NormalMode& m ) = 0;
};

class BuiltinObjectActionsProvider
  : public PopupActionProvider
{
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

class NameObjectActionsProvider
  : public PopupActionProvider
{
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

class BuiltinDocumentActionsProvider
  : public PopupActionProvider
{
  int mnumberofcoordsystems;
  bool misfullscreen;
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

class ObjectConstructorActionsProvider
  : public PopupActionProvider
{
  std::vector<ObjectConstructor*> mctors[NormalModePopupObjects::NumberOfMenus];
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

class PropertiesActionsProvider
  : public PopupActionProvider
{
  // we don't really need NumberOfMenus vectors, but this is the
  // easiest way to do it, and I'm too lazy to do it properly ;)
  std::vector<int> mprops[NormalModePopupObjects::NumberOfMenus];
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

class ObjectTypeActionsProvider
  : public PopupActionProvider
{
  int mnoa;
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};

#ifdef KIG_ENABLE_PYTHON_SCRIPTING
#include "../scripting/script-common.h"
#include "../scripting/script_mode.h"
#include "../scripting/python_type.h"

class ScriptActionsProvider
  : public PopupActionProvider
{
  int mns;
public:
  void fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree );
  bool executeAction( int menu, int& id, const std::vector<ObjectHolder*>& os,
                      NormalModePopupObjects& popup,
                      KigPart& doc, KigWidget& w, NormalMode& m );
};
#endif

NormalModePopupObjects::NormalModePopupObjects( KigPart& part,
                                                KigWidget& view,
                                                NormalMode& mode,
                                                const std::vector<ObjectHolder*>& objs,
						const TQPoint& plc )
  : TDEPopupMenu( &view ), mplc( plc ), mpart( part ), mview( view ), mobjs( objs ),
    mmode( mode ), monlylabels( false )
{
  bool empty = objs.empty();
  bool single = objs.size() == 1;
  connect( this, TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( toplevelMenuSlot( int ) ) );

  TQString title;
  if ( empty )
    title = i18n( "Kig Document" );
  else if ( single )
  {
    if ( !objs[0]->name().isNull() )
      title = TQString::fromLatin1( "%1 %2" ).arg( objs[0]->imp()->type()->translatedName() ).arg( objs[0]->name() );
    else
      title = objs[0]->imp()->type()->translatedName();
  }
  else
    title = i18n( "%1 Objects" ).arg( objs.size() );
  insertTitle( title, 1 );

  if ( !empty )
  {
    monlylabels = true;
    uint i = 0;
    while ( i < objs.size() && monlylabels )
    {
      monlylabels &= objs[i]->imp()->inherits( TextImp::stype() );
      ++i;
    }
  }

  if ( empty )
  {
    // provides some diverse stuff like "unhide all", set coordinate
    // system etc.
    mproviders.push_back( new BuiltinDocumentActionsProvider() );
  };
  // construct an object using these objects and start constructing an
  // object using these objects
  mproviders.push_back( new ObjectConstructorActionsProvider() );
  if ( single )
    mproviders.push_back( new NameObjectActionsProvider() );
  if ( ! empty )
  {
    // stuff like hide, show, delete, set size, set color..
    mproviders.push_back( new BuiltinObjectActionsProvider() );
    // show property as text label -> show menu
    // and construct property's as objects -> construct menu
    mproviders.push_back( new PropertiesActionsProvider() );
    // stuff like "redefine point" for a fixed or constrained point..
    mproviders.push_back( new ObjectTypeActionsProvider() );
  }
#ifdef KIG_ENABLE_PYTHON_SCRIPTING
  // script action..
  mproviders.push_back( new ScriptActionsProvider() );
#endif

  for ( uint i = 0; i < NumberOfMenus; ++i )
    mmenus[i] = new TQPopupMenu( this );

  connect( mmenus[TransformMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( transformMenuSlot( int ) ) );
  connect( mmenus[TestMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( testMenuSlot( int ) ) );
  connect( mmenus[ConstructMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( constructMenuSlot( int ) ) );
  connect( mmenus[StartMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( startMenuSlot( int ) ) );
  connect( mmenus[ShowMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( showMenuSlot( int ) ) );
  connect( mmenus[SetColorMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( setColorMenuSlot( int ) ) );
  connect( mmenus[SetSizeMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( setSizeMenuSlot( int ) ) );
  connect( mmenus[SetStyleMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( setStyleMenuSlot( int ) ) );
  connect( mmenus[SetCoordinateSystemMenu], TQT_SIGNAL( activated( int ) ),
           this, TQT_SLOT( setCoordinateSystemMenuSlot( int ) ) );

  for ( int i = 0; i <= NumberOfMenus; ++i )
  {
    int nextfree = 10;
    for ( uint j = 0; j < mproviders.size(); ++j )
      mproviders[j]->fillUpMenu( *this, i, nextfree );
  };
  static const TQString menunames[NumberOfMenus] =
    {
      i18n( "&Transform" ),
      i18n( "T&est" ),
      i18n( "Const&ruct" ),
      i18n( "&Start" ),
      i18n( "Add Te&xt Label" ),
      i18n( "Set Co&lor" ),
      i18n( "Set &Pen Width" ),
      i18n( "Set St&yle" ),
      TQString(),
      i18n( "Set Coordinate S&ystem" )
    };
  static const TQString menuicons[NumberOfMenus] =
    {
      "centralsymmetry",
      "test",
      TQString(),
      "launch",
      "kig_text",
      "color_fill",
//      "colorize",
      "sizer",
      "paintbrush",
      TQString(),
      TQString()
    };
  int index = 1;
  for ( int i = 0; i < NumberOfMenus; ++i )
  {
    if ( mmenus[i]->count() == 0 ) continue;
    if ( menuicons[i].isNull() )
      insertItem( menunames[i], mmenus[i], i, index++ );
    else
    {
      TDEIconLoader* l = part.instance()->iconLoader();
      TQPixmap icon = l->loadIcon( menuicons[i], TDEIcon::Small, 22, TDEIcon::DefaultState, 0L, true );
      insertItem( TQIconSet( icon ), menunames[i], mmenus[i], i, index++ );
    }
  };
}

void NormalModePopupObjects::testMenuSlot( int i )
{
  activateAction( TestMenu, i );
}

void NormalModePopupObjects::transformMenuSlot( int i )
{
  activateAction( TransformMenu, i );
}

void NormalModePopupObjects::constructMenuSlot( int i )
{
  activateAction( ConstructMenu, i );
}

void NormalModePopupObjects::startMenuSlot( int i )
{
  activateAction( StartMenu, i );
}

void NormalModePopupObjects::showMenuSlot( int i )
{
  activateAction( ShowMenu, i );
}

void NormalModePopupObjects::toplevelMenuSlot( int i )
{
  activateAction( ToplevelMenu, i );
}

void NormalModePopupObjects::activateAction( int menu, int action )
{
  bool done = false;
  // we need action - 10 cause we called fillUpMenu with nextfree set
  // to 10 initially..
  action -= 10;
  for ( uint i = 0; ! done && i < mproviders.size(); ++i )
    done = mproviders[i]->executeAction( menu, action, mobjs, *this, mpart, mview, mmode );
}

NormalModePopupObjects::~NormalModePopupObjects()
{
  delete_all ( mproviders.begin(), mproviders.end() );
}

static const TQColor* colors[] =
{
  &TQt::blue,
  &TQt::black,
  &TQt::gray,
  &TQt::red,
  &TQt::green,
  &TQt::cyan,
  &TQt::yellow,
  &TQt::darkRed
};
static const int numberofcolors = sizeof( colors ) / sizeof( TQColor* );

void BuiltinObjectActionsProvider::fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    TDEIconLoader* l = popup.part().instance()->iconLoader();
    std::vector<ObjectHolder*> os = popup.objects();

    /*
     * mp: we want the "show" action to be visible only
     * if we selected only one object (to be conservative)
     * and if that object is currently hidden.
     * conversely for one hidden object we don't want
     * the "hide" action to be inserted.
     * in any case we have a fixed 'id' associated
     * with the two actions.
     */

    if ( os.size() > 1 || os.front()->shown() )
    {
      popup.addAction( menu, i18n( "&Hide" ), nextfree );
    }
    if ( os.size() == 1 && !os.front()->shown() )
    {
      popup.addAction( menu, i18n( "&Show" ), nextfree+1 );
    }
    nextfree += 2;
    TQPixmap p = l->loadIcon( "move", TDEIcon::Toolbar );
    popup.addAction( menu, p, i18n( "&Move" ), nextfree++ );
    p = l->loadIcon( "edit-delete", TDEIcon::Toolbar );
    popup.addAction( menu, p, i18n( "&Delete" ), nextfree++ );
  }
  else if ( menu == NormalModePopupObjects::SetColorMenu )
  {
    TQPixmap p( 50, 20 );
    for( const TQColor** c = colors; c < colors + numberofcolors; ++c )
    {
      p.fill( **c );
      popup.addAction( menu, p, nextfree++ );
    }
    popup.addAction( menu, i18n( "&Custom Color" ), nextfree++ );
  }
  else if ( menu == NormalModePopupObjects::SetSizeMenu && !popup.onlyLabels() )
  {
    bool point = true;
    bool samecolor = true;
    std::vector<ObjectHolder*> os = popup.objects();
    TQColor color = os.front()->drawer()->color();
    for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
    {
      if ( ! (*i)->imp()->inherits( PointImp::stype() ) )
        point = false;
      if ( (*i)->drawer()->color() != color ) samecolor = false;
    };
    if ( ! samecolor ) color = TQt::blue;
    TQPixmap p( point ? 20 : 50, 20 );
    for ( int i = 1; i < 8; ++i )
    {
      p.fill( popup.eraseColor() );
      TQPainter ptr( &p );
      ptr.setPen( TQPen( color, 1 ) );
      ptr.setBrush( TQBrush( color, TQt::SolidPattern ) );
      if ( point )
      {
        int size = 2*i;
        TQRect r( ( 20 - size ) / 2,  ( 20 - size ) / 2, size, size );
        ptr.drawEllipse( r );
      }
      else
      {
        ptr.setPen( TQPen( color, -1 + 2*i ) );
        ptr.drawLine( TQPoint( 0, 10 ), TQPoint( 50, 10 ) );
      };
      ptr.end();
      popup.addAction( menu, p, nextfree++ );
    };
  }
  else if ( menu == NormalModePopupObjects::SetStyleMenu && !popup.onlyLabels() )
  {
    bool samecolor = true;
    int npoints = 0;
    int nothers = 0;
    std::vector<ObjectHolder*> os = popup.objects();
    TQColor color = os.front()->drawer()->color();
    for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
    {
      if ( (*i)->imp()->inherits( PointImp::stype() ) )
        npoints++;
      else
        nothers++;
      if ( (*i)->drawer()->color() != color ) samecolor = false;
    };
    bool point = ( npoints > nothers );
    if ( ! samecolor ) color = TQt::blue;
    if ( point )
      for ( int i = 0; i < 5; ++i )
      {
        TQPixmap p( 20, 20 );
        p.fill( popup.eraseColor() );
        ScreenInfo si( Rect( -1, -1, 2, 2 ), p.rect() );
        KigPainter ptr( si, TQT_TQPAINTDEVICE(&p), popup.part().document(), false );
        PointImp pt( Coordinate( 0, 0 ) );
        ObjectDrawer d( color, -1, true, Qt::SolidLine, i );
        d.draw( pt, ptr, false );
        popup.addAction( menu, p, nextfree++ );
      }
    else
    {
      Qt::PenStyle penstyles[] = {Qt::SolidLine, Qt::DashLine, Qt::DashDotLine, Qt::DashDotDotLine, Qt::DotLine};
      for ( int i = 0; i < (int) ( sizeof( penstyles ) / sizeof( Qt::PenStyle ) ); ++i )
      {
        TQPixmap p( 50, 20 );
        p.fill( popup.eraseColor() );
        ScreenInfo si( Rect( -2.5, -1, 5, 2 ), p.rect() );
        KigPainter ptr( si, TQT_TQPAINTDEVICE(&p), popup.part().document(), false );
        LineImp line( Coordinate( -1, 0 ), Coordinate( 1, 0 ) );
        Qt::PenStyle ps = penstyles[i];
        ObjectDrawer d( color, -1, true, ps, 1 );
        d.draw( line, ptr, false );
        popup.addAction( menu, p, nextfree++ );
      };
    }
  }
}

void NameObjectActionsProvider::fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    popup.addAction( menu, i18n( "Set &Name..." ), nextfree++ );
  }
  else if ( menu == NormalModePopupObjects::ShowMenu )
  {
    popup.addAction( menu, i18n( "&Name" ), nextfree++ );
  }
}

static void addNameLabel( ObjectCalcer* object, ObjectCalcer* namecalcer, const Coordinate& loc, KigPart& doc )
{
  std::vector<ObjectCalcer*> args;
  args.push_back( namecalcer );
  const bool namelabelneedsframe = false;
  ObjectCalcer* attachto = 0;
  if ( object->imp()->inherits( PointImp::stype() ) || 
       object->imp()->attachPoint().valid() || 
       object->imp()->inherits( CurveImp::stype() ) )
    attachto = object;
  ObjectHolder* label = ObjectFactory::instance()->attachedLabel(
      TQString::fromLatin1( "%1" ), attachto, loc, namelabelneedsframe, args, doc.document() );
  doc.addObject( label );
}

bool NameObjectActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os, NormalModePopupObjects& popup,
  KigPart& doc, KigWidget& w, NormalMode& )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    if ( id >= 1 )
    {
      id -= 1;
      return false;
    }
    assert( os.size() == 1 );
    TQString name = os[0]->name();
    bool ok;
    TQRegExp re(  ".*" );
    TQRegExpValidator* rev = new TQRegExpValidator(  re,  &doc );
    TQString caption = i18n( "Set Object Name" );
    TQString label = i18n( "Set Name of this Object:" );
#if KDE_IS_VERSION( 3, 1, 90 )
    name = KInputDialog::getText( caption, label, name, &ok, &w, 0, rev );
#else
    name = KLineEditDlg::getText( caption, label, name, &ok, &w, rev );
#endif
    if ( ok )
    {
      bool justadded = false;
      ObjectCalcer* namecalcer = os[0]->nameCalcer();
      if ( !namecalcer )
      {
        justadded = true;
        ObjectConstCalcer* c = new ObjectConstCalcer( new StringImp( i18n( "<unnamed object>" ) ) );
        os[0]->setNameCalcer( c );
        namecalcer = c;
      }
      assert( dynamic_cast<ObjectConstCalcer*>( namecalcer ) );
      ObjectConstCalcer* cnamecalcer = static_cast<ObjectConstCalcer*>( os[0]->nameCalcer() );
      MonitorDataObjects mon( cnamecalcer );
      cnamecalcer->setImp( new StringImp( name ) );
      KigCommand* kc = new KigCommand( doc, i18n( "Set Object Name" ) );
      mon.finish( kc );
      doc.history()->addCommand( kc );

      // if we just added the name, we add a label to show it to the user.
      if ( justadded )
        addNameLabel( os[0]->calcer(), namecalcer,
//                    w.fromScreen( w.mapFromGlobal( popup.mapToGlobal( TQPoint( 5, 0 ) ) ) ),
                      w.fromScreen( popup.plc() ),
                      doc );
    }
    return true;
  }
  else if ( menu == NormalModePopupObjects::ShowMenu )
  {
    if ( id >= 1 )
    {
      id -= 1;
      return false;
    }
    assert( os.size() == 1 );
    ObjectCalcer* namecalcer = os[0]->nameCalcer();
    if ( !namecalcer )
    {
      ObjectConstCalcer* c = new ObjectConstCalcer( new StringImp( i18n( "<unnamed object>" ) ) );
      os[0]->setNameCalcer( c );
      namecalcer = c;
    }
    addNameLabel( os[0]->calcer(), namecalcer,
//                  w.fromScreen( w.mapFromGlobal( popup.mapToGlobal( TQPoint( 5, 0 ) ) ) ), doc );
                  w.fromScreen( popup.plc() ), doc );
    return true;
  }
  else
  {
    return false;
  }
}

bool BuiltinObjectActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os, NormalModePopupObjects& popup,
  KigPart& doc, KigWidget& w, NormalMode& mode )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    if ( id > 3 )
    {
      id -= 4;
      return false;
    };
    switch( id )
    {
    case 0:
      // hide the objects..
      doc.hideObjects( os );
      break;
    case 1:
      // show the objects..
      doc.showObjects( os );
      break;
    case 2:
    {
      // move
      TQCursor::setPos( popup.mapToGlobal( TQPoint( 0, 0 ) ) );
      TQPoint p = w.mapFromGlobal( TQCursor::pos() );
      Coordinate c = w.fromScreen( p );
      MovingMode m( os, c, w, doc );
      doc.runMode( &m );
      // in this case, we return, cause we don't want objects to be
      // unselected... ( or maybe we do ? )
      return true;
    }
    case 3:
      // delete
      doc.delObjects( os );
      break;
    default: assert( false );
    };
    mode.clearSelection();
    return true;
  }
  else if ( menu == NormalModePopupObjects::SetColorMenu )
  {
    if ( id >= numberofcolors + 1 )
    {
      id -= numberofcolors + 1;
      return false;
    };
    TQColor color;
    if ( id < numberofcolors )
      color = *colors[id];
    else
    {
      if ( os.size() == 1 )
        color = os.front()->drawer()->color();
      int result = KColorDialog::getColor( color, &w );
      if ( result != KColorDialog::Accepted ) return true;
    }
    KigCommand* kc = new KigCommand( doc, i18n( "Change Object Color" ) );
    assert( color.isValid() );
    for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
      kc->addTask( new ChangeObjectDrawerTask( *i, ( *i )->drawer()->getCopyColor( color ) ) );
    doc.history()->addCommand( kc );
    mode.clearSelection();
    return true;
  }
  else if ( menu == NormalModePopupObjects::SetSizeMenu )
  {
    if ( id >= 7 )
    {
      id -= 7;
      return false;
    };

    KigCommand* kc = new KigCommand( doc, i18n( "Change Object Width" ) );
    for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
      kc->addTask( new ChangeObjectDrawerTask( *i, ( *i )->drawer()->getCopyWidth( 1 + 2 * id ) ) );
    doc.history()->addCommand( kc );
    mode.clearSelection();
    return true;
  }
  else if ( menu == NormalModePopupObjects::SetStyleMenu )
  {
    int npoints = 0;
    int nothers = 0;
    for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
    {
      if ( (*i)->imp()->inherits( PointImp::stype() ) )
        npoints++;
      else
        nothers++;
    };
    bool point = ( npoints > nothers );
    int max = point ? 5 : 5;
    if ( id >= max )
    {
      id -= max;
      return false;
    };

    if ( point )
    {
      KigCommand* kc = new KigCommand( doc, i18n( "Change Point Style" ) );
      for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
        if ( (*i)->imp()->inherits( PointImp::stype() ) )
          kc->addTask( new ChangeObjectDrawerTask( *i, ( *i )->drawer()->getCopyPointStyle( id ) ) );
      doc.history()->addCommand( kc );
      mode.clearSelection();
      return true;
    }
    else
    {
      Qt::PenStyle penstyles[] = {Qt::SolidLine, Qt::DashLine, Qt::DashDotLine, Qt::DashDotDotLine, Qt::DotLine};
      assert( id < (int)( sizeof( penstyles ) / sizeof( Qt::PenStyle ) ) );
      Qt::PenStyle p = penstyles[id];
      KigCommand* kc = new KigCommand( doc, i18n( "Change Object Style" ) );
      for ( std::vector<ObjectHolder*>::const_iterator i = os.begin(); i != os.end(); ++i )
        if ( ! (*i)->imp()->inherits( PointImp::stype() ) )
          kc->addTask( new ChangeObjectDrawerTask( *i, ( *i )->drawer()->getCopyStyle( p ) ) );
      doc.history()->addCommand( kc );
      mode.clearSelection();
    }
    return true;
  }
  else return false;
}

void ObjectConstructorActionsProvider::fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree )
{
  const KigDocument& d = popup.part().document();
  const KigWidget& v = popup.widget();
  typedef ObjectConstructorList::vectype vectype;
  vectype vec = ObjectConstructorList::instance()->constructors();

  for ( vectype::iterator i = vec.begin(); i != vec.end(); ++i )
  {
    bool add = false;
    if ( popup.objects().empty() )
    {
      add = menu == NormalModePopupObjects::StartMenu && ! (*i)->isTransform() && ! (*i)->isTest();
    }
    else
    {
      int ret = (*i)->wantArgs( getCalcers( popup.objects() ), d, v );
      if ( ret == ArgsParser::Invalid ) continue;
      if ( (*i)->isTransform() && popup.objects().size() == 1 ) add = menu == NormalModePopupObjects::TransformMenu;
      else if ( (*i)->isTest() ) add = menu == NormalModePopupObjects::TestMenu;
      else if ( ( *i )->isIntersection() ) add = menu == NormalModePopupObjects::ToplevelMenu;
      else if ( ret == ArgsParser::Complete ) add = menu == NormalModePopupObjects::ConstructMenu;
      else add = menu == NormalModePopupObjects::StartMenu;
    };
    if ( add )
    {
      TQCString iconfile = (*i)->iconFileName();
      if ( !iconfile.isEmpty() && !iconfile.isNull() )
      {
        TQPixmap icon = popup.part().instance()->iconLoader()->loadIcon( iconfile, TDEIcon::Toolbar, 22, TDEIcon::DefaultState, 0L, true );
        popup.addAction( menu, icon, (*i)->descriptiveName(), nextfree++ );
      }
      else
        popup.addAction( menu, (*i)->descriptiveName(), nextfree++ );
      mctors[menu].push_back( *i );
    }
  };
}

bool ObjectConstructorActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os,
  NormalModePopupObjects&,
  KigPart& doc, KigWidget& w, NormalMode& m )
{
  if ( (uint) id >= mctors[menu].size() )
  {
    id -= mctors[menu].size();
    return false;
  }

  ObjectConstructor* ctor = mctors[menu][id];
  std::vector<ObjectCalcer*> osc = getCalcers( os );
  if ( ! os.empty() && ctor->wantArgs( osc, doc.document(), w ) == ArgsParser::Complete )
  {
    ctor->handleArgs( osc, doc, w );
    m.clearSelection();
  }
  else
  {
    BaseConstructMode* mode = ctor->constructMode( doc );
    mode->selectObjects( os, w );
    doc.runMode( mode );
    delete mode;
  };
  return true;
}

void NormalModePopupObjects::addAction( int menu, const TQPixmap& pix, int id )
{
  TQPopupMenu* m = 0;
  if ( menu == ToplevelMenu ) m = this;
  else m = mmenus[menu];
  int ret = m->insertItem( pix, id );
  assert( ret == id );
  // pretend to use this var..
  (void) ret;
}

void NormalModePopupObjects::setColorMenuSlot( int i )
{
  activateAction( SetColorMenu, i );
}

void NormalModePopupObjects::setSizeMenuSlot( int i )
{
  activateAction( SetSizeMenu, i );
}

void NormalModePopupObjects::setStyleMenuSlot( int i )
{
  activateAction( SetStyleMenu, i );
}

void NormalModePopupObjects::setCoordinateSystemMenuSlot( int i )
{
  activateAction( SetCoordinateSystemMenu, i );
}

void NormalModePopupObjects::addAction( int menu, const TQPixmap& icon, const TQString& name, int id )
{
  TQPopupMenu* m = 0;
  if ( menu == ToplevelMenu ) m = this;
  else m = mmenus[menu];
  int ret = m->insertItem( TQIconSet( icon ), name, id );
  assert( ret == id );
  // pretend to use this var..
  (void)ret;
}

void NormalModePopupObjects::addAction( int menu, const TQString& name, int id )
{
  TQPopupMenu* m = 0;
  if ( menu == ToplevelMenu ) m = this;
  else m = mmenus[menu];
  int ret = m->insertItem( name, id );
  assert( ret == id );
  // pretend to use this var..
  (void)ret;
}

PopupActionProvider::~PopupActionProvider()
{
}

void PropertiesActionsProvider::fillUpMenu( NormalModePopupObjects& popup,
                                            int menu, int& nextfree )
{
  if ( popup.objects().size() != 1 ) return;
  ObjectHolder* o = popup.objects()[0];
  uint np = o->imp()->numberOfProperties();
  if ( menu != NormalModePopupObjects::ConstructMenu &&
       menu != NormalModePopupObjects::ShowMenu ) return;
  for ( uint i = 0; i < np; ++i )
  {
    ObjectImp* prop = o->imp()->property( i, popup.part().document() );
    const char* iconfile = o->imp()->iconForProperty( i );
    bool add = true;
    if ( menu == NormalModePopupObjects::ConstructMenu )
    {
      // we don't want imp's like DoubleImp, since we can't show them
      // anyway..
      add &= ! prop->inherits( BogusImp::stype() );
      // we don't want to construct PointImp's coordinate property,
      // since it would construct a point at the same place as its
      // parent..
      add &= ! ( o->imp()->inherits( PointImp::stype() ) &&
                 prop->inherits( PointImp::stype() ) );
    }
    else if ( menu == NormalModePopupObjects::ShowMenu )
      add &= prop->canFillInNextEscape();
    if ( add )
    {
      if ( iconfile && *iconfile )
      {
        TQPixmap pix = popup.part().instance()->iconLoader()->loadIcon( iconfile, TDEIcon::Toolbar, 22, TDEIcon::DefaultState, 0L, true );
        popup.addAction( menu, pix, i18n( o->imp()->properties()[i] ), nextfree++ );
      }
      else
      {
        popup.addAction( menu, i18n( o->imp()->properties()[i] ), nextfree++ );
      };
      mprops[menu-1].push_back( i );
    };
    delete prop;
  };
}

bool PropertiesActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os,
  NormalModePopupObjects& popup,
  KigPart& doc, KigWidget& w, NormalMode& )
{
  if ( menu != NormalModePopupObjects::ConstructMenu &&
       menu != NormalModePopupObjects::ShowMenu )
    return false;
  if ( (uint) id >= mprops[menu - 1].size() )
  {
    id -= mprops[menu - 1].size();
    return false;
  }
  int propid = mprops[menu-1][id];
  assert( os.size() == 1 );
  ObjectHolder* parent = os[0];
  if ( menu == NormalModePopupObjects::ShowMenu )
  {
    std::vector<ObjectCalcer*> args;
    args.push_back( new ObjectPropertyCalcer( parent->calcer(), propid ) );
    args.back()->calc( doc.document() );
// TODO: recover the cursor position somehow... the following does not work
// in general...
//    Coordinate c = w.fromScreen( w.mapFromGlobal( popup.mapToGlobal( TQPoint( 5, 0 ) ) ) );
// mp: it seems that we have no idea where to position the label, 
// btw what's the meaning of (5,0)?    let the
// attach method decide what to do... (passing an invalidCoord)
//  ///////    Coordinate c = Coordinate::invalidCoord();
    Coordinate c = w.fromScreen( popup.plc() );
    ObjectHolder* label = ObjectFactory::instance()->attachedLabel(
      TQString::fromLatin1( "%1" ), parent->calcer(), c,
      false, args, doc.document() );
    doc.addObject( label );
  }
  else
  {
    ObjectHolder* h = new ObjectHolder(
      new ObjectPropertyCalcer( parent->calcer(), propid ) );
    h->calc( doc.document() );
    doc.addObject( h );
  };
  return true;
}

void ObjectTypeActionsProvider::fillUpMenu(
  NormalModePopupObjects& popup, int menu, int& nextfree )
{
  if ( popup.objects().size() != 1 ) return;
  if ( menu != NormalModePopupObjects::ToplevelMenu ) return;
  ObjectHolder* to = popup.objects()[0];
  ObjectTypeCalcer* c = dynamic_cast<ObjectTypeCalcer*>( to->calcer() );
  if ( ! c ) return;
  const ObjectType* t = c->type();

  TQStringList l = t->specialActions();
  mnoa = l.count();
  for ( int i = 0; i < mnoa; ++i )
    popup.addAction( menu, l[i], nextfree++ );
}

bool ObjectTypeActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os,
  NormalModePopupObjects&,
  KigPart& doc, KigWidget& w, NormalMode& m )
{
  if ( menu != NormalModePopupObjects::ToplevelMenu ) return false;
  if ( id >= mnoa )
  {
    id -= mnoa;
    return false;
  }
  assert( os.size() == 1 );
  ObjectTypeCalcer* oc = dynamic_cast<ObjectTypeCalcer*>( os[0]->calcer() );
  assert(  oc );

  oc->type()->executeAction( id, *os[0], *oc, doc, w, m );
  return true;
}

void BuiltinDocumentActionsProvider::fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    popup.addAction( menu, i18n( "U&nhide All" ), nextfree++ );
    popup.part().action( "view_zoom_in" )->plug( &popup );
    popup.part().action( "view_zoom_out" )->plug( &popup );
    popup.part().action( "fullscreen" )->plug( &popup );
    nextfree += 3;
  }
  else if ( menu == NormalModePopupObjects::SetCoordinateSystemMenu )
  {
    int idoffset = nextfree;
    TQStringList l = CoordinateSystemFactory::names();
    mnumberofcoordsystems = l.count();
    for ( uint i = 0; i < l.count(); ++i )
      popup.addAction( menu, l[i], nextfree++ );
    int current = popup.part().document().coordinateSystem().id();
    popup.setChecked( menu, idoffset + current, true );
  }
}

bool BuiltinDocumentActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>&,
  NormalModePopupObjects&,
  KigPart& doc, KigWidget&, NormalMode& m )
{
  if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    kdDebug() << "id: " << id << endl;
    if ( id == 0 )
    {
      doc.showHidden();
      m.clearSelection();
      return true;
    }
    id -= 1;
    return false;
  }
  else if ( menu == NormalModePopupObjects::SetCoordinateSystemMenu )
  {
    if ( id >= mnumberofcoordsystems )
    {
      id -= mnumberofcoordsystems;
      return false;
    };
    CoordinateSystem* sys = CoordinateSystemFactory::build( id );
    assert( sys );
    doc.history()->addCommand( KigCommand::changeCoordSystemCommand( doc, sys ) );
    m.clearSelection();
    return true;
  }
  else return false;
}

void NormalModePopupObjects::setChecked( int menu, int n, bool checked )
{
  mmenus[menu]->setItemChecked( n, checked );
}

#ifdef KIG_ENABLE_PYTHON_SCRIPTING
/**
 * this is a local function that looks for a python script associated
 * to a clicked object
 */
static ObjectTypeCalcer* getPythonExecuteTypeFromCalcer( ObjectCalcer* o )
{
  ObjectTypeCalcer* oc = dynamic_cast<ObjectTypeCalcer *>( o );
  if ( !oc ) return 0;
  const PythonExecuteType* pythonexec = dynamic_cast<const PythonExecuteType*>( oc->type() );
  if ( pythonexec ) return oc;

  return 0;
}

void ScriptActionsProvider::fillUpMenu( NormalModePopupObjects& popup, int menu, int& nextfree )
{
  if ( menu == NormalModePopupObjects::StartMenu )
  {
    TDEIconLoader* l = popup.part().instance()->iconLoader();
    TQPixmap p = l->loadIcon( ScriptType::icon( ScriptType::Python ), TDEIcon::Toolbar, 22, TDEIcon::DefaultState, 0L, true );
    popup.addAction( menu, p, i18n( "Python Script" ), nextfree++ );
    mns++;
  }
  else if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    if ( !popup.objects().empty() &&
         getPythonExecuteTypeFromCalcer( popup.objects().front()->calcer() ) )
    {
      popup.addAction( menu, i18n( "Edit Script..." ), nextfree );
    }
    nextfree++;
  }
}

bool ScriptActionsProvider::executeAction(
  int menu, int& id, const std::vector<ObjectHolder*>& os,
  NormalModePopupObjects&, KigPart& doc, KigWidget& w, NormalMode& mode )
{
  if ( menu == NormalModePopupObjects::StartMenu )
  {
    if ( id == 0 )
    {
      ScriptCreationMode m( doc );
      m.setScriptType( ScriptType::Python );
      if ( os.size() > 0 )
      {
        mode.clearSelection();
        m.addArgs( os, w );
        m.goToCodePage();
      }
      doc.runMode( &m );
      return true;
    }
    else
    {
      id -= mns;
    }
  }
  else if ( menu == NormalModePopupObjects::ToplevelMenu )
  {
    if ( id == 0 )
    {
      ObjectTypeCalcer* oc = getPythonExecuteTypeFromCalcer( os.front()->calcer() );
      if ( oc )
      {
        ScriptEditMode m( oc, doc );
        m.setScriptType( ScriptType::Python );
        doc.runMode( &m );
      }
      return true;
    }
    else
    {
      id -= 1;
    }
  }

  return false;
}
#endif

int ObjectChooserPopup::getObjectFromList( const TQPoint& p, KigWidget* w,
                                           const std::vector<ObjectHolder*>& objs,
                                           bool givepopup )
{
  int size = objs.size();

  // no objects
  if ( size == 0 )
    return -1;

  int id = -1;

  int numpoints = 0;
  int numpolygons = 0;
  int numothers = 0;

  for ( std::vector<ObjectHolder*>::const_iterator i = objs.begin();
        i != objs.end(); ++i )
  {
    if ( (*i)->imp()->inherits( PointImp::stype() ) ) numpoints++;
    else if ( (*i)->imp()->inherits( PolygonImp::stype() ) ) numpolygons++;
    else numothers++;
  }

  // simply cases:
  // - only 1 point ( and eventually other objects )
  // - no points and an object which is not a polygon
  // - only one object
  // FIXME: we assume that our objects are sorted ( points, others, polygons )!
  if ( ( numpoints == 1 ) ||
       ( ( numpoints == 0 ) && ( numothers == 1 ) ) ||
       ( size == 1 ) )
    id = 0;
  else
  {
    if ( givepopup )
    {
      ObjectChooserPopup* ppp = new ObjectChooserPopup( p, *w, objs );
      ppp->exec( TQCursor::pos() );

      id = ppp->mselected;

      delete ppp;
      ppp = 0;
    }
    else
    {
      // we don't want to show a popup to the user, so let's give a
      // value > 0 to indicate that it's not the first
      id = 1;
    }
  }
//  kdDebug() << "numpoints: " << numpoints << endl
//            << "numothers: " << numothers << endl
//            << "numpolygons: " << numpolygons << endl
//            << "id: " << id << endl;

  return id;
}

ObjectChooserPopup::ObjectChooserPopup( const TQPoint& p, KigWidget& view,
                                        const std::vector<ObjectHolder*>& objs )
  : TDEPopupMenu(), mplc( p ), mview( view ), mobjs( objs ), mselected( -1 )
{
  for ( uint i = 0; i < mobjs.size(); i++ )
  {
    insertItem( !mobjs[i]->name().isEmpty()
                ? TQString::fromLatin1( "%1 %2" ).arg( mobjs[i]->imp()->type()->translatedName() ).arg( mobjs[i]->name() )
                : mobjs[i]->imp()->type()->translatedName(),
                i );
  }

  connect( this, TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( actionActivatedSlot( int ) ) );
}

ObjectChooserPopup::~ObjectChooserPopup()
{
}

void ObjectChooserPopup::actionActivatedSlot( int which )
{
  mselected = which;
}
