/****************************************************************************
** $Id: quickobjects.cpp  beta1   edited Dec 13 19:19 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "quickobjects.h"

#include <qsfunction.h>
#include <qsoperations.h>
#include <qsenv.h>
#include <qsinternal.h>
#include <qsstring_object.h>
#include <qsdate_object.h>
#include "quickinterpreter.h"
#include "quickdispatchobject.h"
#include <qmainwindow.h>
#include <private/qucomextra_p.h>
#include "quickbytearrayobject.h"
#include "quickpixmapobject.h"
#include "quickcolorobject.h"
#include "quickfontobject.h"
#include "quickcoordobjects.h"
#include <qsarray_object.h>
#include <qdatetime.h>
#include <qwidgetfactory.h>
#include "../engine/qsfuncref.h"
#include <qapplication.h>
#include <qmetaobject.h>
#include <qobjectlist.h>
#include <qptrdict.h>
#include <qregexp.h>
#include <private/qcom_p.h>
#include <private/qpluginmanager_p.h>

using namespace QS;

static QSObject uObjectToQS( QUObject *o, const void *extra=0 );
static bool qsToUObject(  QUObject *o, const QSObject &v, QUType *t, const void *extra=0 );

// ### temporary convenience function
static QuickInterpreter *interpreter()
{
    return QuickInterpreter::self();
}

static QValueList<QuickMetaData> getSlots( QObject* o, const char *s, bool super )
{
    QValueList<QuickMetaData> mds;
    const QMetaObject* meta = o->metaObject();
    QStrList slotList = meta->slotNames( super );
    int slen = qstrlen( s );
    int index = -1;
    const char *sl = slotList.first();
    for ( ; sl; sl = slotList.next() ) {
	index++;
	int pos = strchr( sl, '(' ) - sl;
	if ( slen == pos && !qstrncmp( sl, s, pos-1 ) )  { // param less match
	    if ( sl ) { // ### why this check ? (Harri)
		const QMetaData* md = meta->slot( index, super );
		if ( md && md->access != QMetaData::Private )
		    mds.append( QuickMetaData( md, index + (super?0:meta->slotOffset()) ) );
	    }
	}
    }

    return mds;
}

QObjectList *extraChildren( QObject *wid )
{
    QObjectList *extra = 0;
    QObject *o;
    if ( wid->isA( "QMainWindow" ) ) {
	extra = new QObjectList;
	QWidget *w = ( (QMainWindow*)wid )->centralWidget();
	if ( w && w->children() ) {
	    QObjectListIt it( *w->children() );
	    while ( it.current() ) {
		extra->append( it.current() );
		++it;
	    }
	}
	QObjectList *l = wid->queryList( "QToolBar" );
	if ( l ) {
	    QObjectListIt it( *l );
	    while ( it.current() ) {
		extra->append( it.current() );
		++it;
	    }
	}
	delete l;
	l = wid->queryList( "QPopupMenu" );
	if ( l ) {
	    QObjectListIt it( *l );
	    while ( it.current() ) {
		extra->append( it.current() );
		++it;
	    }
	}
	delete l;
    } else if ( ( o = wid->child( 0, "QLayoutWidget" ) ) ) {
	if ( o->children() ) {
	    if ( !extra )
		extra = new QObjectList;
	    QObjectListIt it( *o->children() );
	    while ( it.current() ) {
		extra->append( it.current() );
		++it;
	    }
	}
    }
    return extra;
}

QObject *objectChild( QObject *wid, const char *objName, const char *inheritsClass, bool recursiveSearch )
{
    QObject *o = wid->child( objName, inheritsClass, recursiveSearch );
    if ( o )
	return o;
    if ( !o ) {
	if ( wid->isA( "QMainWindow" ) && ( objName || inheritsClass ) ) {
	    QObjectList *l = extraChildren( wid );
	    if ( l ) {
		QObjectListIt it( *l );
		while ( it.current() ) {
		    if ( ( !objName || qstrcmp( objName, it.current()->name() ) == 0 ) &&
			 ( !inheritsClass || it.current()->inherits( inheritsClass ) ) ) {
			delete l;
			return it.current();
		    }
		    ++it;
		}
	    }
	    delete l;
	} else if ( ( o = wid->child( 0, "QLayoutWidget", FALSE ) ) ) {
	    return o->child( objName, inheritsClass, FALSE );
	}
    }
    // ### not nice, but try to be as good as possible
    return wid->child( objName, inheritsClass, TRUE );
}

/////////////////// internal functions /////////////////////////////

QSObject executeSlot( QObject *qobj, const QValueList<QuickMetaData> &mds,
		      const QSList &args )
{
    QValueList<QuickMetaData>::ConstIterator md = mds.begin();
    const QUMethod *m = 0;
    bool firstIsReturn = FALSE;
    bool ok = FALSE;
    QUObject *uo = 0;
    while ( md != mds.end() ) {
	m = (*md).method;
	if ( m && m->count && m->parameters[0].inOut == QUParameter::Out )
	    firstIsReturn = TRUE;
	if ( args.size() != ( m->count - ( firstIsReturn ? 1 : 0 ) ) ) {
	    ++md;
	    m = 0;
	    firstIsReturn = FALSE;
	    continue;
	}

	uo = new QUObject[ m->count + ( firstIsReturn ? 0 : 1 ) ];
	const QUParameter *p = m->parameters;
	ok = TRUE;
	for ( int i = 0; i < m->count; i++ ) {
	    if ( firstIsReturn && !i )
		continue;
	    if ( !qsToUObject( &uo[ i + ( firstIsReturn ? 0 : 1 ) ],
			       args[ i - ( firstIsReturn ? 1 : 0 ) ],
			       p[ i ].type, p[ i ].typeExtra ) ) {
		if ( QUType::isEqual( p[ i ].type, &static_QUType_QVariant ) ) {
		    QVariant var = qsToVariant( args[ i - ( firstIsReturn ? 1 : 0 ) ],
						(QVariant::Type)*(char*)p[i].typeExtra );
		    if ( var.type() != QVariant::Invalid )
			static_QUType_QVariant.set( &uo[ i + ( firstIsReturn ? 0 : 1 ) ],
						    var );
		} else if ( QUType::isEqual( p[ i ].type, &static_QUType_varptr ) ) {
		    QVariant var = qsToVariant( args[ i - ( firstIsReturn ? 1 : 0 ) ],
						(QVariant::Type)*(char*)p[i].typeExtra );
		    ok = var.type() == (QVariant::Type)*(char*)p[i].typeExtra;
		    if ( ok )
			static_QUType_varptr.set( &uo[ i + ( firstIsReturn ? 0 : 1 ) ],
				  var.rawAccess( 0, QVariant::Invalid, TRUE ) );
		} else {
		    ok = FALSE;
		    break;
		}
	    }
	}
	if ( ok ) {
	    break;
	} else {
	    ++md;
	    m = 0;
	    firstIsReturn = FALSE;
	    delete [] uo;
	}
    }

    if ( !ok ) {
	QString msg( "No matching overload found. Following overloads are possible:\n" );
	md = mds.begin();
	while ( md != mds.end() ) {
	    msg += "    " + QString( (*md).name ) + "\n";
	    ++md;
	}
	msg.remove( msg.length() - 1, 1 );
	return QSEnv::current()->throwError( TypeError, msg );
    }

    QSObject ret = QSUndefined();
    qobj->qt_invoke( (*md).id, uo );
    ret = firstIsReturn ? uObjectToQS( uo, m->parameters[0].typeExtra ) : QSUndefined();

    delete [] uo;
    return ret;
}

QSWrapperShared::~QSWrapperShared()
{
    invalidate();
    Q_ASSERT( objects.isEmpty() && receivers.isEmpty() );
}

void QSWrapperShared::invalidate()
{
    QuickScriptReceiver *r;
    QMap<QObject*, QuickScriptReceiver*>::Iterator it = receivers.begin();
    while ( it != receivers.end() ) {
 	r = *it;
 	++it;
	delete r;
    }
    receivers.clear();
    // let QObject lose the reference to us
    if ( udata ) {
	udata->invalidate();
	udata = 0;
    }
    for ( uint i = 1; i < objects.count(); i++ )
	delete objects[ i ];

    objects.resize( 0 );
}

EventId QSWrapperShared::findEventId( const QString &event )
{
    QString ev = event.left( event.find( '(' ) );
    const char *e = ev.latin1();
    int l = ev.length();
    for ( int i = (int)objects.count()-1; i >= 0; --i ) {
	const QMetaObject *meta = objects[ i ]->metaObject();
	int n = meta->numSignals( TRUE );
	for ( int j = n - 1; j >= 0; --j ) {
	    const QMetaData *sig = meta->signal( j, TRUE );
	    int pos = strchr( sig->name, '(' ) - sig->name;
	    if ( l == pos && !qstrncmp( sig->name, e, pos-1 ) ) {
		const QUMethod *m = sig->method;
		int mc = m->count;
		if ( m && m->count && m->parameters[0].inOut == QUParameter::Out )
		    mc--;
		QString ee( event.mid( event.find( '(' ) + 1 ) );
		ee = ee.left( ee.find( ')' ) );
		ee = ee.simplifyWhiteSpace();
		QStringList lst = QStringList::split( ',', ee );
		if ( (int)lst.count() != mc )
		    continue;
		QStringList::ConstIterator sit = lst.begin();
		bool ok = TRUE;
		for ( int k = ( mc == m->count ? 0 : 1); k < mc; ++k, ++sit ) {
		    QString arg = *sit;
		    int asterix = arg.find( '*' );
		    if ( asterix != -1 )
			arg.remove( asterix, 1 );
		    int ampersand = arg.find( '&' );
		    if ( ampersand != -1 )
			arg.remove( ampersand, 1 );
		    if ( arg.startsWith( "const" ) ) {
			arg.remove( 0, 5 );
			arg = arg.simplifyWhiteSpace();
		    }
		    QStringList al = QStringList::split( ':', arg );
		    arg = al[(int)al.count() - 1].simplifyWhiteSpace();
		    QuickInterpreter::cleanTypeRev( arg );
		    QString type = m->parameters[k].type->desc();
		    bool isPtrType = type == "ptr" || type == "varptr";
		    if ( type == "ptr" )
			type = (const char*)m->parameters[k].typeExtra;
		    else if ( type == "varptr" )
			type = QVariant::typeToName( (QVariant::Type)*(char*)m->parameters[k].typeExtra );
		    if ( arg != type && !( isPtrType && ( arg == "ptr" || arg == "varptr" ) ) ) {
			ok = FALSE;
			break;
		    }
		}
		if ( !ok )
		    continue;
		return EventId( j, i );
	    }
	}
    }
    return EventId();
}

void QSWrapperShared::setEventHandler( const QString &event,
				       QObject *ctx, const QString &func, QSObject qsctx )
{
    EventId evid = findEventId( event );
    if ( evid.id == -1 )
	return;
    QMap<QObject*, QuickScriptReceiver*>::ConstIterator it = receivers.find( objects[ evid.obj ] );
    QuickScriptReceiver *r = 0;
    if ( it == receivers.end() ) {
	r = new QuickScriptReceiver( objects[ evid.obj ] );
	receivers.insert( objects[ evid.obj ], r );
    } else {
	r = *it;
    }
    r->setEventHandler( evid.id, ctx, func, qsctx );
}

void QSWrapperShared::removeEventHandler( const QString &event,
				       QObject *ctx, const QString &func, QSObject qsctx )
{
    EventId evid = findEventId( event );
    if ( evid.id == -1 )
	return;
    QMap<QObject*, QuickScriptReceiver*>::Iterator it = receivers.find( objects[ evid.obj ] );
    if ( it == receivers.end() )
	return;
    (*it)->removeEventHandler( evid.id, ctx, func, qsctx );
}

QSWrapperClass::QSWrapperClass( QSClass *b )
    : QSWritableClass( b )
{
}

QSWrapperClass::~QSWrapperClass()
{
    invalidate();
}

QSWrapperShared *QSWrapperClass::shared( const QSObject *obj ) const
{
    Q_ASSERT( obj->isA( this ) );
    return (QSWrapperShared*)obj->shVal();
}

QPtrVector<QObject>& QSWrapperClass::objectVector( const QSObject *obj ) const
{
    return shared( obj )->objects;
}

void QSWrapperClass::invalidate()
{
//     QPtrDictIterator<QSWrapperShared> it( *dict );
//     QSWrapperShared *sh;
//     while ( ( sh = it.current() ) ) {
// 	QObject::disconnect( (QObject*)it.currentKey(), SIGNAL( destroyed( QObject* ) ),
// 			     this, SLOT( objectDestroyed( QObject* ) ) );
// 	++it;
// 	sh->invalidate( this );
// 	// sh is deleted by reference counting
//     }
//     dict->clear();
}

void QSWrapperClass::deref( QSObject * /*o*/ ) const
{
#if 0 && defined(QS_LEAK)
    o->shVal()->deref();
    if ( o->shVal()->count==0 ) {
	((QSWrapperClass*)this)->invalidate();
 	delete o->shVal();
 	o->setVal( (QSShared*)0 );
    }
#endif
}

bool QSWrapperClass::member( const QSObject *objPtr, const QString &p,
			     QSMember *mem ) const
{
    if ( !objPtr )
	return QSWritableClass::member( objPtr, p, mem );

    mem->setType( QSMember::Custom );
    mem->setOwner( this );
    mem->setName( p );

    QSWrapperShared *sh = shared( objPtr );
    const QPtrVector<QObject> &objects = sh->objects;

    if ( objects.isEmpty() )
	return QSWritableClass::member( objPtr, p, mem );

    QString key;
    // cache lookup
    QMap<QString, QSOT::QuickScriptObjectType>::ConstIterator it2
	= sh->hasPropCache.find( p );
    if ( it2 != sh->hasPropCache.end() ) {
	if ( *it2 == QSOT::Object ) {
	    for ( int i = (uint)objects.count()-1; i >= 0; i-- ) {
		QObject *obj = objectChild( objects[ i ], p.ascii(), "QObject", FALSE );
		if ( obj )
		    return TRUE;
	    }
	} else if ( *it2 != QSOT::Unknown ) {
	    key = p;
	    QMap<QString,QuickScriptProperty>::Iterator it =
		sh->propertyCache.find( key );
	    if ( it != sh->propertyCache.end() ) {
		switch ( (*it).type ) {
		case QSOT::Property: {
		    QVariant v;
		    const QMetaProperty *mp = (*it).id.var->metaProperty();
		    objects[ (*it).objNum ]->qt_property( mp->id(), 1, &v );
		    if ( !mp->writable() )
			mem->setWritable( FALSE );
		    if ( mp->isEnumType() ) {
			return TRUE;
		    } else {
			if ( v != (*it).id.var->value() )
			    (*it).id.var = new QuickScriptVariant( v, (*it).id.var->metaProperty() );
			return TRUE;
		    }
		}
		case QSOT::Slot:
		    mem->setExecutable( TRUE );
		    mem->setWritable( FALSE );
		    return TRUE;
		case QSOT::Enum:
		    mem->setWritable( FALSE );
		    return TRUE;
		default:
		    break;
		}
		return QSWritableClass::member( objPtr, p, mem );
	    } else {
		int i;
		switch ( *it2 ) {
		case QSOT::Property: {
		    for ( i = (uint)objects.count()-1; i >= 0; i-- ) {
			const QMetaProperty *mp =
			    objects[ i ]->metaObject()->
			    property( objects[ i ]->metaObject()->findProperty( p.ascii(), i == 0 ), i == 0 );
			if ( mp ) {
			    QVariant v;
			    objects[ i ]->qt_property( mp->id(), 1, &v );
			    if ( !mp->writable() )
				mem->setWritable( FALSE );
			    if ( mp->isEnumType() ) {
				return TRUE;;
			    } else {
				QuickScriptVariant *var = new QuickScriptVariant( v, mp );
				sh->propertyCache.
				    replace( key, QuickScriptProperty( QSOT::Property, var, i ) );
				return TRUE;
			    }
			}
		    }
		} break;
		case QSOT::Slot: {
		    for ( i = int(objects.count())-1; i >= 0; i-- ) {
			QValueList<QuickMetaData> mds = getSlots( objects[ i ], p.ascii(), i == 0 );
			if ( !mds.isEmpty() ) {
			    mem->setExecutable( TRUE );
			    mem->setWritable( FALSE );
			    QuickScriptObjectFunc *func = 0; // new QuickScriptObjectFunc( objects[ i ], mds );
			    sh->propertyCache.
				replace( key, QuickScriptProperty( QSOT::Slot, func, i ) );
			    return TRUE;
			}
		    }
		case QSOT::Enum:
		    for ( i = int(objects.count())-1; i >= 0; i-- ) {
			QStrList enums = objects[ i ]->metaObject()->enumeratorNames( i == 0 );
			for ( int k = 0; k < (int)enums.count(); ++k ) {
			    const QMetaEnum *me =
				objects[ i ]->metaObject()->enumerator( enums.at( k ), i == 0 );
			    for ( int l = 0; l < (int)me->count; ++l ) {
				if ( qstrcmp( me->items[l].key, p.ascii() ) == 0 ) {
				    mem->setWritable( FALSE );
				    sh->propertyCache.
					replace( key,
						 QuickScriptProperty( QSOT::Enum,
								      me->items[l].value, i ) );
				    return TRUE;
				}
			    }
			}
		    }
		} break;
		default:
		    break;
		}
	    }
	} else {
	    return QSWritableClass::member( objPtr, p, mem );
	}
    }

    // no cache hit. doing a real search now starting with enums.
    QStrList enums = objects[ 0 ]->metaObject()->enumeratorNames( TRUE );
    for ( int k = 0; k < (int)enums.count(); ++k ) {
	const QMetaEnum *me = objects[ 0 ]->metaObject()->enumerator( enums.at( k ), TRUE );
	for ( int l = 0; l < (int)me->count; ++l ) {
	    if ( qstrcmp( me->items[l].key, p.ascii() ) == 0 ) {
		mem->setWritable( FALSE );
		sh->hasPropCache.replace( p, QSOT::Property );
		sh->propertyCache.
		    replace( p, QuickScriptProperty( QSOT::Enum, me->items[l].value, 0 ) );
		return TRUE;
	    }
	}
    }

    // search QObject properties, childs etc.
    for ( int i = int(objects.count())-1; i >= 0; i-- ) {
	QObject *o = objects[ i ];
	const QMetaObject *meta = o->metaObject();
	const QMetaProperty *mp = meta->
	    property( meta->findProperty( p.ascii(), i == 0 ), i == 0 );
	if ( mp ) {
	    if ( key.isEmpty() )
		key = p;
	    if ( !mp->writable() )
		mem->setWritable( FALSE );
	    sh->hasPropCache.replace( p, QSOT::Property );
	    QVariant v;
	    o->qt_property( mp->id(), 1, &v );
	    if ( mp->isEnumType() ) {
		return TRUE;
	    } else {
		QuickScriptVariant *var = new QuickScriptVariant( v, mp );
		sh->propertyCache.
		    replace( key, QuickScriptProperty( QSOT::Property, var, i ) );
		return TRUE;
	    }
	}

	QObject *cobj = objectChild( o, p.ascii(), "QObject", FALSE );
	if ( cobj ) {
	    sh->hasPropCache.replace( p, QSOT::Object );
	    return TRUE;
	}

	QValueList<QuickMetaData> mds = getSlots( o, p.ascii(), i == 0 );
	if ( !mds.isEmpty() ) {
	    if ( key.isEmpty() )
		key = p;
	    mem->setExecutable( TRUE );
	    mem->setWritable( FALSE );
	    QuickScriptObjectFunc *func = 0; // new QuickScriptObjectFunc( objects[ i ], mds );
	    sh->propertyCache.
		replace( key, QuickScriptProperty( QSOT::Slot, func, i ) );
	    sh->hasPropCache.replace( p, QSOT::Slot );
	    return TRUE;
	}

	QStrList enums = meta->enumeratorNames( i == 0 );
	for ( int k = 0; k < (int)enums.count(); ++k ) {
	    const QMetaEnum *me = meta->enumerator( enums.at( k ), i == 0 );
	    for ( int l = 0; l < (int)me->count; ++l ) {
		if ( qstrcmp( me->items[l].key, p.ascii() ) == 0 ) {
		    sh->propertyCache.
			replace( p, QuickScriptProperty( QSOT::Enum, me->items[l].value, i ) );
		    return TRUE;
		}
	    }
	}
    }

    sh->hasPropCache.replace( p, QSOT::Unknown );

    return QSWritableClass::member( objPtr, p, mem );
}

QSObject QSWrapperClass::fetchValue( const QSObject *o,
				     const QSMember &mem ) const
{
    // member specific to this class ?
    if ( mem.type() != QSMember::Custom )
	return QSWritableClass::fetchValue( o, mem );

    QSWrapperShared *sh = shared( o );
    const QPtrVector<QObject> &objects = sh->objects;
    QString p = mem.name();

    QMap<QString, QSOT::QuickScriptObjectType>::ConstIterator it2
	= sh->hasPropCache.find( p );
    Q_ASSERT( it2 != sh->hasPropCache.end() ); // filled my member()
    Q_ASSERT( *it2 != QSOT::Unknown );

    if ( *it2 == QSOT::Object ) {
	for ( int i = int(objects.count())-1; i >= 0; i-- ) {
	    QObject *obj = objectChild( objects[ i ], p.ascii(), "QObject", FALSE );
	    if ( obj )
		return interpreter()->wrap( obj );
	    }
    } else {
	QMap<QString,QuickScriptProperty>::Iterator it =
	    sh->propertyCache.find( p );
	Q_ASSERT( it != sh->propertyCache.end() );  // filled my member()
	switch ( (*it).type ) {
	case QSOT::Property: {
	    QVariant v;
	    const QMetaProperty *mp = (*it).id.var->metaProperty();
	    objects[ (*it).objNum ]->qt_property( mp->id(), 1, &v );
	    if ( mp->isEnumType() ) {
		return QSString( mp->valueToKey( v.toInt() ) );
	    } else {
		if ( v != (*it).id.var->value() )
		    (*it).id.var = new QuickScriptVariant( v, (*it).id.var->metaProperty() );
		if ( (*it).id.var->isNative() )
		    return (*it).id.var->toNative();
		return  *(*it).id.var;
	    }
	}
	case QSOT::Slot:
	    return env()->funcRefClass()->createReference( *o, mem );
	case QSOT::Enum:
	    return QSNumber( (*it).id.enumValue );
	default:
	    break;
	}
    }

    qWarning( "QSWrapperClass::write: shouldn't get here" );
    return QSUndefined();
}

QSObject QSWrapperClass::invoke( QSObject *objPtr, const QSMember &mem,
				 const QSList &args ) const
{
    if ( mem.type() != QSMember::Custom )
	return QSWritableClass::invoke( objPtr, mem, args );

    Q_ASSERT( mem.owner() == this );
    QSWrapperShared *sh = shared( objPtr );
    const QPtrVector<QObject> &objects = sh->objects;
    const char *asc = mem.name().ascii();

    for ( int i = int(objects.count())-1; i >= 0; i-- ) {
	QValueList<QuickMetaData> mds = getSlots( objects[ i ], asc, i == 0 );
	if ( !mds.isEmpty() ) {
	    return executeSlot( objects[ i ], mds, args );
	}
    }

    qWarning( "QSWraperClass::invoke: shouldn't get here" );
    return QSUndefined();
}

void QSWrapperClass::write( QSObject *objPtr, const QSMember &mem,
			    const QSObject &val ) const
{
    Q_ASSERT ( mem.isWritable() );

    // member specific to this class ?
    if ( mem.type() != QSMember::Custom ) {
	QSWritableClass::write( objPtr, mem, val );
	return;
    }

    QSWrapperShared *sh = shared( objPtr );
    const QPtrVector<QObject> &objects = sh->objects;
    QVariant var = qsToVariant( val, QVariant::Invalid );
    const char *n = mem.name().ascii();

    for ( uint i = 0; i < objects.size(); i++ ) {
	QObject *obj = objects[ i ];
	const QMetaObject *meta = obj->metaObject();
	const QMetaProperty *prop =
	    meta->property( meta->findProperty( n, i == 0 ), i == 0 );
	if ( !prop )
	    continue;

	if ( !prop->writable() ) {
	    // shouldn't happen since writable is set to false by member()
	    qWarning( "QuickScriptObject::put: property %s not writable.", n );
	    return;
	}

	if ( prop->isEnumType() ) {
	    if ( var.type() == QVariant::Double )
		var.asInt();
	    if ( !obj->setProperty( n, var ) ) {
		qWarning( "QuickScriptObject::put( %s ) setting enum failed.", n );
		// ### provide more descriptive message. Like 'invalid type x',
		// or 'unkown enum. possible enums are x, y and z'.
	    }
	    return;
	}
	bool b = obj->setProperty( n, var );
	if ( !b ) {
	    qWarning( "QuickScriptObject::put( %s ) failed.", n );
	}
	return;
    }
    QSWritableClass::write( objPtr, mem, val );
}

bool QSWrapperClass::toBoolean( const QSObject *obj ) const
{
    return !objectVector( obj ).isEmpty();
}

QString QSWrapperClass::toString( const QSObject *obj ) const
{
    QPtrVector<QObject> &objects = objectVector( obj );
    if ( objects.isEmpty() )
	return "[object QObject]";
    return "[object " + QString( objects[0]->className() ) + "]";
}

QString QSWrapperClass::debugString( const QSObject *obj ) const
{
    QPtrVector<QObject> &objects = objectVector( obj );
    QString s = "{";
    bool first = TRUE;
    uint i;
    for ( i = 0; i < objects.count(); i++ ) {
	if ( objects[i]->isA( "QuickNamespace" ) )
	    continue;
	const QObjectList *ch = objects[i]->children();
	if ( ch ) {
	    QObjectListIt it( *ch );
	    while ( it.current() ) {
		QObject *o = it.current();
		if ( o->inherits( "QLayoutWidget" ) ||
		     o-> inherits( "QWidgetStack" ) ||
		     o->inherits( "Spacer" ) ||
		     o->inherits( "QSizeGrip" ) ||
		     o->inherits( "QWidgetStack" ) ) {
		    ++it;
		    continue;
		}
		QString n = o->name();
		if ( n.find( " " ) != -1 || n.find( "qt_" ) != -1 || n.find( "unnamed" ) != -1 ) {
		    ++it;
		    continue;
		}
		QSObject wrp = interpreter()->wrap( o );
		if ( !first )
		    s += ",";
		first = FALSE;
		s += wrp.objectType()->identifier() + "=" + wrp.debugString();
		++it;
	    }
	}
    }

    for ( i = 0; i < objects.count(); i++ ) {
	if ( objects[i]->isA( "QuickNamespace" ) )
	    continue;
	const QMetaObject *meta = objects[i]->metaObject();
	for ( int j = 0; j < meta->numProperties( TRUE ); ++j ) {
	    const QMetaProperty *mp = meta->property( j, TRUE );
	    if ( i != 0 && qstrcmp( mp->name(), "name" ) == 0 )
		continue;
	    if ( !first )
		s += ",";
	    first = FALSE;
	    s += mp->name();
	    s += "=";
	    s += objects[i]->property( mp->name() ).toString();
	    s += ":";
	    QString t = mp->type();
	    QuickInterpreter::cleanType( t );
	    s += t;
	}
    }
    s += "}:" + QString( objects[0]->className() );
    return s;
}

/*!
  \reimp
  Handles two special cases: a) a comparison of references to QObjects
  no matter whether the QObjects still or already use a detached wrapper
  class. b) distinct pointer wrapper objects containing identical pointers
 */

int QSWrapperClass::isEqual( const QSObject &a, const QSObject &b ) const
{
    Q_ASSERT( a.objectType() == this );
    if ( !b.isA( interpreter()->wrapperClass() ) &&
	 !b.isA( interpreter()->pointerClass() ) )
	return -1;

    // check whether the references point to the same QObject
    QSWrapperClass *aclass = (QSWrapperClass*)a.objectType();
    QSWrapperClass *bclass = (QSWrapperClass*)b.objectType();
    QPtrVector<QObject>& ova = aclass->objectVector( &a );
    QPtrVector<QObject>& ovb = bclass->objectVector( &b );
    if ( ova.isEmpty() && ovb.isEmpty() ) // both null pointers ?
	return TRUE;
    return !ova.isEmpty() && !ovb.isEmpty() && ova[0] == ovb[0];
}

QSObject QSWrapperClass::wrap( const QPtrVector<QObject> &objs )
{
    QSWrapperShared *sh = new QSWrapperShared( this );
    sh->objects = objs;
    return QSObject( this, sh );
}

QSWrapperShared* QSWrapperClass::createShared( QObject *o ) const
{
    Q_ASSERT( o );
    QSWrapperShared *shared = new QSWrapperShared( this );

    bool b = QuickInterpreter::queryDispatchObjects( o, shared->objects );
    Q_ASSERT( b );

    return shared;
}

class NormalizeObject : public QObject
{
public:
    NormalizeObject() : QObject() {}
    static QCString normalizeSignalSlot( const char *signalSlot ) { return QObject::normalizeSignalSlot( signalSlot ); }
};

QSObjectConstructor::QSObjectConstructor( QSClass *b,
					  const QString &className, Type t )
    : QSClass( b ), cname( className ), type( t )
{
}

QSObject QSObjectConstructor::construct( const QSList &args ) const
{
    if ( type == Class ) {
	QPtrVector<QObject> result;
	QValueList<QVariant> vargs;
	for ( int i = 0; i < args.size(); i++ )
	    vargs.append( qsToVariant( args[i], QVariant::Invalid ) );
	bool success = QuickInterpreter::construct( cname, vargs, result );
	if ( success && result.size() >= 1 )
	    success = QuickInterpreter::queryDispatchObjects( result[0], result );
	if ( result.size() == 0 || !result[0] )
	    return env()->throwError( GeneralError,
					QString( "Could not construct " ) + cname +
					QString( ". Invalid constructor arguments were specified" ),
					-1 );

	if ( result[0]->metaObject() && !result[0]->inherits( "QuickPtrDispatchObject" ) )
	    return interpreter()->wrapperClass()->wrap( result );
	return interpreter()->pointerClass()->
	    wrapPointer( (QuickPtrDispatchObject*)result[ 0 ] );
    } else if ( type == Form ) {
	QWidget *parentWidget = 0;
	const char *name = 0;
	if ( args.size() > 0 ) {
	    if ( args[0].isA( "QObject" ) ) {
		QSObject lo = args[0];
		QObject *o = interpreter()->wrapperClass()->
			     objectVector( &lo )[0];
		if ( o->isWidgetType() )
		    parentWidget = (QWidget*)o;
	    }
	    if ( args.size() > 1 && args[1].type() == StringType ) {
		name = qstrdup( args[1].toString().latin1() );
	    }
	}

	for ( QMap<QWidget*, QString>::ConstIterator it = qwf_forms->begin(); it != qwf_forms->end(); ++it ) {
	    if ( cname == QString( it.key()->name() ) ) {
		QPtrVector<QObject> result;
		qwf_language = new QString( "Qt Script" );
		QWidget *w = QWidgetFactory::create( *it, 0, parentWidget, name );
		delete qwf_language;
		qwf_language = 0;
		int idx = result.size();
		result.resize( idx + 1 );
		result.insert( idx, w );
		return interpreter()->wrapperClass()->wrap( result );
	    }
	}
    }
    return QSUndefined();
}

/////////////////// pointer wrapper /////////////////////////////

QSObject QSPointerClass::wrapPointer( QuickPtrDispatchObject *ptr )
{
    QSWrapperShared *sh = new QSWrapperShared( this );
    sh->objects.resize( 1 );
    sh->objects.insert( 0, ptr );
    return QSObject( this, sh );
}


QSObject QSPointerClass::wrapPointer( const char *n, void *p )
{
    QSWrapperShared *sh = new QSWrapperShared( this );
    if ( p ) { // no interfaces for null pointers
	bool b = QuickInterpreter::queryDispatchObjects( n, p, sh->objects );
	if ( !b )
	    qWarning( "QSPointerClass: didn't find an interface "
		      "for %s pointer.", n );
    }
    return QSObject( this, sh );
}

QString QSPointerClass::toString( const QSObject *obj ) const
{
    const QPtrVector<QObject> &objects = objectVector( obj );
    if ( objects.isEmpty() )
	return "[object Pointer]";
    return QSWrapperClass::toString( obj );
}

double QSPointerClass::toNumber( const QSObject *o ) const
{
    return (ulong)pointer( o );
}

bool QSPointerClass::toBoolean( const QSObject *o ) const
{
    return pointer( o ) != 0;
}

QString QSPointerClass::debugString( const QSObject *obj ) const
{
    const QPtrVector<QObject> &objects = objectVector( obj );
    if ( objects.isEmpty() )
	return "{}:Pointer";
    else
	return QSWrapperClass::debugString( obj );
}

void* QSPointerClass::pointer( const QSObject *obj ) const
{
    const QPtrVector<QObject> &objects = objectVector( obj );
    if ( objects.isEmpty() )
	return 0;
    return ((QuickPtrDispatchObject*)objects[ 0 ])->pointer();
}

const char* QSPointerClass::pointerType( const QSObject *obj ) const
{
    const QPtrVector<QObject> &objects = objectVector( obj );
    if ( objects.isEmpty() )
	return "void";
    return objects[ 0 ]->name();
}

/////////////////////////////////////////////////////////////////////////////

QSVariantShared::QSVariantShared( const QVariant &v, const QMetaProperty *m )
    : variant( v ),
      mp( m )
{
    native = ( v.type() == QVariant::Int ||
	       v.type() == QVariant::UInt ||
	       v.type() == QVariant::Bool ||
	       v.type() == QVariant::Double ||
	       v.type() == QVariant::String ||
	       v.type() == QVariant::StringList ||
	       v.type() == QVariant::List ||
	       v.type() == QVariant::Map ||
	       v.type() == QVariant::Date ||
	       v.type() == QVariant::Time ||
	       v.type() == QVariant::DateTime ||
	       v.type() == QVariant::Rect ||
	       v.type() == QVariant::Size ||
	       v.type() == QVariant::Point ||
	       v.type() == QVariant::Color ||
	       v.type() == QVariant::Font ||
	       v.type() == QVariant::ByteArray ||
	       v.type() == QVariant::Pixmap
	);
}

QSVariantShared *QSVariantClass::shared( const QSObject *obj ) const
{
    Q_ASSERT( obj->objectType() == this );
    return (QSVariantShared*)obj->shVal();
}

QVariant *QSVariantClass::variant( const QSObject *obj ) const
{
    return &shared( obj )->variant;
}

QSObject QSVariantClass::construct( const QSList & ) const
{
    // there's no "Variant" object available to the user so
    // this function should never get called. Theoretically.
    qWarning( "QSVariantClass::construct( const QSList& ) called" );
    return construct( QVariant(), 0 );
}

QSObject QSVariantClass::construct( const QVariant &v,
				    const QMetaProperty *m ) const
{
    return QSObject( this, new QSVariantShared( v, m ) );
}

/////////////////////////////////////////////////////////////////////////////

QuickScriptVariant::QuickScriptVariant( const QVariant &v,
					const QMetaProperty *m )
    : QSObject( interpreter()->variantClass(), new QSVariantShared( v, m ) )
{
}

QSVariantShared *QuickScriptVariant::shared() const
{
    return (QSVariantShared*)shVal();
}

bool QuickScriptVariant::isNative() const
{
    return (bool)shared()->native;
}

QSObject QuickScriptVariant::toNative() const
{
    return toPrimitive( StringType );
}

QVariant QuickScriptVariant::value() const
{
    return shared()->variant;
}

const QMetaProperty *QuickScriptVariant::metaProperty() const
{
    return shared()->mp;
}

void QSVariantShared::createObject()
{
    // ##### complete for other types
    const QVariant& var = variant;
    switch ( var.type() ) {
    case QVariant::ByteArray:
	iobj = interpreter()->byteArrayClass()->construct( var.toByteArray() );
	break;
    case QVariant::Pixmap:
	iobj = interpreter()->pixmapClass()->construct( var.toPixmap() );
	break;
    case QVariant::Date: {
	QSList l;
	l.append( QSNumber( var.toDate().year() ) );
	l.append( QSNumber( var.toDate().month() + 1 ) );
	l.append( QSNumber( var.toDate().day() ) );
	iobj = QSEnv::current()->dateClass()->construct( l );
	break;
    }
    case QVariant::Time: {
	QSList l;
	l.append( QSNumber( QDate::currentDate().year() ) );
	l.append( QSNumber( QDate::currentDate().month() + 1 ) );
	l.append( QSNumber( QDate::currentDate().day() ) );
	l.append( QSNumber( var.toTime().hour() ) );
	l.append( QSNumber( var.toTime().minute() ) );
	l.append( QSNumber( var.toTime().second() ) );
	l.append( QSNumber( var.toTime().msec() ) );
	iobj = QSEnv::current()->dateClass()->construct( l );
	break;
    }
    case QVariant::DateTime: {
	QSList l;
	l.append( QSNumber( var.toDateTime().date().year() ) );
	l.append( QSNumber( var.toDateTime().date().month() + 1 ) );
	l.append( QSNumber( var.toDateTime().date().day() ) );
	l.append( QSNumber( var.toDateTime().time().hour() ) );
	l.append( QSNumber( var.toDateTime().time().minute() ) );
	l.append( QSNumber( var.toDateTime().time().second() ) );
	l.append( QSNumber( var.toDateTime().time().msec() ) );
	iobj = QSEnv::current()->dateClass()->construct( l );
	break;
    }
    case QVariant::Color:
	iobj = interpreter()->colorClass()->construct( var.toColor() );
	break;
    case QVariant::Font:
	iobj = interpreter()->fontClass()->construct( var.toFont() );
	break;
    case QVariant::Point:
	iobj = interpreter()->pointClass()->construct( var.toPoint() );
	break;
    case QVariant::Size:
	iobj = interpreter()->sizeClass()->construct( var.toSize() );
	break;
    case QVariant::Rect:
	iobj = interpreter()->rectClass()->construct( var.toRect() );
	break;
    case QVariant::StringList: {
	QStringList lst = var.toStringList();
	QSArray array;
	int i = 0;
	for ( QStringList::ConstIterator it = lst.begin();
	      it != lst.end(); ++it )
	    array.put( QString::number( i++ ), QSString( *it ) );
	iobj = array;
	break;
    }
    case QVariant::List: {
	QValueList<QVariant> lst = var.toList();
	QSArray array;
	int i = 0;
	for ( QValueList<QVariant>::ConstIterator it = lst.begin();
	      it != lst.end(); ++it )
	    array.put( QString::number( i++ ), QuickScriptVariant( *it, 0 ) );
	iobj = array;
	break;
    }
    case QVariant::Map: {
	QMap<QString, QVariant> map = var.toMap();
	QSArray array;
	for ( QMap<QString, QVariant>::ConstIterator it = map.begin();
	      it != map.end(); ++it )
	    array.put( it.key(), QuickScriptVariant( *it, 0 ) );
	iobj = array;
	break;
    }
    case QVariant::String:
    case QVariant::CString:
	iobj = QSString( var.toString() );
	break;
    default:
	iobj = QSObject(); // null !
	break;
    }
}

QSObject QSVariantClass::toPrimitive( const QSObject *obj, Type ) const
{
    QSObject res;
    QVariant *var = variant( obj );
    QSVariantShared *sh = shared( obj );
    switch ( var->type() ) {
    case QVariant::Bool:
	return QSBoolean( var->toBool() );
    case QVariant::Int:
	return QSNumber( var->toInt() );
    case QVariant::UInt:
	return QSNumber( var->toUInt() );
    case QVariant::Double:
	return QSNumber( var->toDouble() );
    case QVariant::CString:
    case QVariant::String:
	return QSString( var->toString() );
    case QVariant::StringList:
    case QVariant::List:
    case QVariant::Map:
    case QVariant::Date:
    case QVariant::Time:
    case QVariant::DateTime:
    case QVariant::Rect:
    case QVariant::Size:
    case QVariant::Point:
    case QVariant::Color:
    case QVariant::Font:
    case QVariant::ByteArray:
    case QVariant::Pixmap:
	sh->createObject();
	return sh->iobj;
    default:
	// fallback for cases not handled above
	return QSString( toString( obj ) );
    }
}

bool QSVariantClass::toBoolean( const QSObject *obj ) const
{
    QVariant *var = variant( obj );
    switch ( var->type() ) {
    case QVariant::CString:
	return !var->toCString().isEmpty();
    case QVariant::String:
	return !var->toString().isEmpty();
    default:
	return var->toBool();
    }
}

double QSVariantClass::toNumber( const QSObject *obj ) const
{
    QVariant *var = variant( obj );
    switch ( var->type() ) {
    case QVariant::Int:
    case QVariant::Bool:
	return double( var->toInt() );
    case QVariant::UInt:
	return double( var->toUInt() );
    case QVariant::Double:
	return double( var->toDouble() );
    case QVariant::String:
	return QString( var->toString() ).toDouble();
    case QVariant::Color:
	return double( var->toColor().rgb() );
    case QVariant::Date:
	// QDate -> UTC conversion
	return -1000.0 * QDateTime( var->toDate() ).
	    secsTo( QDate( 1970, 1, 1 ) );
    default:
	//qWarning( "QuickScriptVariant::toNumber: unhandled QVariant type." );
	return NaN;
    }
}

QString QSVariantClass::toString( const QSObject *obj ) const
{
    QVariant *var = variant( obj );
    QString str;
    switch ( var->type() ) {
    case QVariant::Bool:
	return var->toBool() ? "true" : "false";
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::Double:
 	return QSString::from( var->toDouble() );
    case QVariant::CString:
    case QVariant::String:
	return var->toString();
    case QVariant::Color:
	return var->toColor().name();
    case QVariant::ByteArray: {
	QString str;
	QByteArray ba = var->toByteArray();
	for ( int i = 0; i < (int)ba.size() ; ++i )
	    str += ba.data()[i];
	return str;
    }
    case QVariant::Point: {
	QPoint point = var->toPoint();
	return QString( "(%1, %2)" ).arg( point.x() ).arg( point.y() );
    }
    case QVariant::Rect: {
	QRect rect = var->toRect();
	return QString( "(%1, %2, %3, %4)" ).arg( rect.x() ).arg( rect.y() ).
	    arg( rect.width() ).arg( rect.height() );
    }
    case QVariant::Size: {
	QSize size = var->toSize();
 	return QString( "(%1, %2)" ).arg( size.width() ).arg( size.height() );
    }
    case QVariant::Date:
	return var->toDate().toString();
    case QVariant::Time:
	return var->toTime().toString();
    case QVariant::Font:
	return var->toFont().toString();
    case QVariant::DateTime:
	return var->toDateTime().toString();
    case QVariant::StringList:
	return var->toStringList().join( "," );
    case QVariant::List: {
	QValueList<QVariant> lst = var->toList();
	QString str;
	bool first = TRUE;
	for ( QValueList<QVariant>::ConstIterator it = lst.begin();
	      it != lst.end(); ++it ) {
	    if ( !first )
		str += ",";
	    str += (*it).toString();
	    first = FALSE;
	}
	return str;
    }
    case QVariant::Map: {
	QMap<QString, QVariant> map = var->toMap();
	QString str;
	bool first = TRUE;
	for ( QMap<QString, QVariant>::ConstIterator it = map.begin();
	      it != map.end(); ++it ) {
	    if ( !first )
		str += ",";
	    str += it.key() + "=" + (*it).toString();
	    first = FALSE;
	}
	return str;
    }
    default:
	return "undefined";
    }
}

QString QSVariantClass::debugString( const QSObject *obj ) const
{
    QVariant *var = variant( obj );
    switch ( var->type() ) {
    case QVariant::Bool:
	return var->toBool() ? "true:Boolean" : "false:Boolean";
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::Double:
 	return QSString::from( var->toDouble() ) + ":Number";
    case QVariant::CString:
    case QVariant::String:
	return var->toString() + ":String";
    case QVariant::Color:
	return var->toColor().name() + ":Color";
    case QVariant::Point: {
	QPoint point = var->toPoint();
	return QString( "{x=%1:Number,y=%2:Number}:Point" ).
	    arg( point.x() ).arg( point.y() );
    };
    case QVariant::Rect: {
	QRect rect = var->toRect();
	return QString( "{x=%1:Number,y=%2:Number,width=%3:Number,"
			"height=%4:Number}:Rect" ).arg( rect.x() ).
	    arg( rect.y() ). arg( rect.width() ).arg( rect.height() );
    };
    case QVariant::Size: {
	QSize size = var->toSize();
 	return QString( "{width=%1:Number=,height=%2:Number}:Size" ).
	    arg( size.width() ).arg( size.height() );
    };
    case QVariant::Date:
	return var->toDate().toString() + ":Date";
    case QVariant::Time:
	return var->toTime().toString() + ":Date";
    case QVariant::DateTime:
	return var->toDateTime().toString() + ":Date";
    case QVariant::Font: {
	QFont font = var->toFont();
	return QString( "{family=%1:String,pointSize=%2:Number,bold=%3:Boolean,italic=%4:Boolean,underline=%5:Boolean}:Font" ).
	      arg( font.family() ).arg( font.pointSize() ).
	      arg( QString::number( (uint)font.bold() ) ).
	      arg( QString::number( (uint)font.italic() ) ).
	      arg( QString::number( (uint)font.underline() ) );
    };
    case QVariant::StringList:
	return var->toStringList().join( "," ) + ":StringList";
    case QVariant::List: {
	QValueList<QVariant> lst = var->toList();
	QString str = "{";
	bool first = TRUE;
	for ( QValueList<QVariant>::ConstIterator it = lst.begin();
	      it != lst.end(); ++it ) {
	    if ( !first )
		str += ",";
	    str += (*it).toString() + ":" + (*it).typeName();
	    first = FALSE;
	}
	return str + "}:List";
    }
    case QVariant::Map: {
	QMap<QString, QVariant> map = var->toMap();
	QString str = "{";
	bool first = TRUE;
	for ( QMap<QString, QVariant>::ConstIterator it = map.begin();
	      it != map.end(); ++it ) {
	    if ( !first )
		str += ",";
	    str += it.key() + "=" + (*it).toString() + ":" + (*it).typeName();
	    first = FALSE;
	}
	return str + "}:Map";
    }
    default:
	// fallback
	return toString( obj );
    }
}

bool QSVariantClass::member( const QSObject *objPtr, const QString &n,
			     QSMember *m ) const
{
    if( !objPtr )
	return FALSE;
    QSVariantShared *sh = shared( objPtr );
    if ( !sh->iobj.isValid() ) {
	sh->createObject();
	if ( !sh->iobj.isValid() )
	    return FALSE;
    }
    return sh->iobj.objectType()->member( &sh->iobj, n, m );
}

QSObject QSVariantClass::fetchValue( const QSObject *objPtr,
				     const QSMember &mem ) const
{
    QSVariantShared *sh = shared( objPtr );
    Q_ASSERT( sh->iobj.isValid() );
    return sh->iobj.objectType()->fetchValue( &sh->iobj, mem );
}

void QSVariantClass::write( QSObject *objPtr, const QSMember &mem,
			    const QSObject &val ) const
{
    QSVariantShared *sh = shared( objPtr );
    Q_ASSERT( sh->iobj.isValid() );
    sh->iobj.objectType()->write( &sh->iobj, mem, val );
    sh->variant = qsToVariant( sh->iobj, QVariant::Invalid );
}

QSObject QSVariantClass::invoke( QSObject * objPtr, const QSMember &mem,
				 const QSList &args ) const
{
    QSVariantShared *sh = shared( objPtr );
    Q_ASSERT( sh->iobj.isValid() );
    return sh->iobj.invoke( mem, args );
}

/////////////////////////////////////////////////////////////////////////////


class TimerObject : public QObject {
public:
    TimerObject( QObject *parent=0, const char *name=0 ) :
	QObject( parent, name ) { }
    static int setTimer( const QSObject &interval, const QSObject &func );
    static void stopTimer( const QSObject &id );
    static void stopTimers();

protected:
    void timerEvent( QTimerEvent * );

private:
    static TimerObject *timer;
    QMap<int, EventTarget> eventMap;
};

TimerObject *TimerObject::timer = 0;

/**
 * Starts a timer with the specified \a interval that will call
 * a function named \a handler each time it fires.
 * This function returns an identifier for the started timer or -1
 * if the arguments were invalid.
 */

int TimerObject::setTimer( const QSObject &interval, const QSObject &func )
{
    if ( !timer ) {
	timer = new TimerObject();
	// ### install clean up handler
    }

    if ( !interval.isDefined() )
	return -1;
    double iv = interval.toNumber();
    if ( isNaN( iv ) )
	return -1;
    int id = timer->startTimer( int(iv) );
    if ( id == 0 )
	return -1;
    Q_ASSERT( func.isExecutable() );
    EventTarget target;	// slight misuse
    target.eng = interpreter();
    target.targets << EventTarget::Target( 0, func, "" );
    timer->eventMap.insert( id, target );
    return id;
}

void TimerObject::stopTimer( const QSObject &id )
{
    if ( timer ) {
	double d = id.toNumber();
	if ( !isNaN( d ) ) {
	    timer->killTimer( int(d) );
	    timer->eventMap.remove( int(d) );
	}
    }
}

void TimerObject::stopTimers()
{
    if ( timer ) {
	timer->killTimers();
	timer->eventMap.clear();
    }
}

// execute the function bound to this timer. passed the id as first argument.
void TimerObject::timerEvent( QTimerEvent *e )
{
    if ( !QuickInterpreter::timersEnabled() )
	return;
    QMap<int, EventTarget>::ConstIterator it = eventMap.find( e->timerId() );
    if ( it == eventMap.end() )
	return;
    QSList args;
    args.append( QSNumber( e->timerId() ) );
    QSObject func = (*it).targets.first().qsctx;
    Q_ASSERT( func.isExecutable() );
    (*it).eng->reinit();
    func.execute( args );
}

QSApplicationClass::QSApplicationClass( QSClass *b )
    : QSWritableClass( b )
{
    addMember( "exit", QSMember( &exit ) );
    if ( qApp ) {
	QSArray args;
	for ( int i = 0; i < qApp->argc(); ++i )
	    args.put( QString::number( i ), QSString( qApp->argv()[ i ] ) );
	    addStaticVariableMember( "argv", args, AttributeNone );
    }
}

bool QSApplicationClass::member( const QSObject *o, const QString &n,
				 QSMember *m ) const
{
    QuickInterpreter *ip = interpreter();
    if ( !ip->children().contains( n ) ) // a top level object ?
	return QSWritableClass::member( o, n, m );

    m->setType( QSMember::Custom );
    m->setOwner( this );
    m->setName( n );
    m->setWritable( FALSE );
    return TRUE;
}

QSObject QSApplicationClass::fetchValue( const QSObject *objPtr,
					 const QSMember &mem ) const
{
    if ( mem.type() != QSMember::Custom )
	return QSWritableClass::fetchValue( objPtr, mem );

    QObjectListIt it( *(interpreter()->topLevelObjects()) );
    QCString n = mem.name().ascii();
    while ( it.current() ) {
	if ( n == (*it)->name() )
	    return interpreter()->wrap( *it );
	++it;
    }
    qWarning( "QSApplicationClass::fetchValue: child widget disappeared" );
    return QSUndefined();
}

QSObject QSApplicationClass::exit( const QSList & )
{
    QApplication::exit(); // ### modify behaviour in devel mode
    return QSUndefined();
}

/*!
  Start a timer that will fire in a number of milliseconds specified
  in the first parameter. Upon firing of the timer the function specified
  in the second parameter will be executed with the id of the timer as
  argument. Returns the id of the timer.
*/
QSObject qsStartTimer( const QSList &args )
{
    QSObject hnd = args[ 1 ];
    if ( hnd.isFunction() ) {
	int id = TimerObject::setTimer( args[ 0 ], hnd );
	return QSNumber( id );
    }
    QString msg = "Can only install functions as event handler";
    return QSEnv::current()->throwError( TypeError, msg );
}

QSObject qsKillTimer( const QSList &args )
{
    TimerObject::stopTimer( args[ 0 ] );
    return QSUndefined();
}

QSObject qsKillTimers( const QSList & )
{
    TimerObject::stopTimers();
    return QSUndefined();
}

////////////////////////////////////////////////////////////////////

QVariant qsToVariant( const QSObject &v, QVariant::Type type )
{
    if ( !v.isValid() )
	return QVariant();

    switch ( v.type() ) {
    case NullType:
    case UndefinedType:
	return QVariant();
	break;
    case BooleanType:
	return QVariant( v.toBoolean(), 0 );
	break;
    case NumberType:
	return QVariant( v.toNumber() );
	break;
    case StringType:
	return QVariant( v.toString() );
	break;
    default:
	if ( v.isA( interpreter()->pointClass() ) )
	    return *interpreter()->pointClass()->point( &v );
	else if ( v.isA( interpreter()->sizeClass() ) )
	    return *interpreter()->sizeClass()->size( &v );
	else if ( v.isA( interpreter()->rectClass() ) )
	    return *interpreter()->rectClass()->rect( &v );
	else if ( v.isA( interpreter()->colorClass() ) )
	    return *interpreter()->colorClass()->color( &v );
	else if ( v.isA( interpreter()->fontClass() ) )
	    return *interpreter()->fontClass()->font( &v );
	else if ( v.isA( interpreter()->byteArrayClass() ) )
	    return *interpreter()->byteArrayClass()->byteArray( &v );
	else if ( v.isA( interpreter()->pixmapClass() ) )
	    return *interpreter()->pixmapClass()->pixmap( &v );
	else if ( v.isA( interpreter()->variantClass() ) )
	    return *interpreter()->variantClass()->variant( &v );
	else if ( v.isA( interpreter()->pointerClass() ) ) {
	    void *ptr = interpreter()->pointerClass()->pointer( &v );
	    QString s = "Pointer:%1:Pointer"; // ### ugly
	    s = s.arg( QString::number( (ulong)ptr ) );
	    return QVariant( s );
	} else if ( v.isA( interpreter()->wrapperClass() ) ) {
	    const QPtrVector<QObject>& objects =
		interpreter()->wrapperClass()->objectVector( &v );
	    QString s = "Pointer:%1:QObject"; // ### ugly
	    s = s.arg( QString::number( (ulong)(objects[0] ) ) );
	    return QVariant( s );
	} else if ( v.isA( "Date" ) ) {
	    // #### is there an easier way to convert the date?
	    QSList args;
	    if ( type == QVariant::Time ) {
		QTime time( v.get( "getHours").execute( args ).toInteger(),
			    v.get( "getMinutes").execute( args ).toInteger(),
			    v.get( "getSeconds").execute( args ).toInteger(),
			    v.get( "getMilliseconds").execute( args ).toInteger() );
		return time;
	    } else if ( type == QVariant::DateTime ) {
		QTime time( v.get( "getHours").execute( args ).toInteger(),
			    v.get( "getMinutes").execute( args ).toInteger(),
			    v.get( "getSeconds").execute( args ).toInteger(),
			    v.get( "getMilliseconds").execute( args ).toInteger() );
		QDate date( v.get( "getFullYear").execute( args ).toInteger(),
			    v.get( "getMonth").execute( args ).toInteger() + 1,
			    v.get( "getDate").execute( args ).toInteger() );
		return QDateTime( date, time );
	    } else {
		QDate date( v.get( "getFullYear").execute( args ).toInteger(),
			    v.get( "getMonth").execute( args ).toInteger() + 1,
			    v.get( "getDate").execute( args ).toInteger() );
		return date;
	    }
	} else if ( v.isA( "Array" ) ) {
	    int len = v.get( "length" ).toInteger();
	    if ( type == QVariant::StringList ) {
		QStringList l;
		for ( int i = 0; i < len; ++i )
		    l << v.get( QString::number( i ) ).toString();
		QVariant var = l;
		return var;
	    } else if ( type == QVariant::List ) {
		QValueList<QVariant> l;
		for ( int i = 0; i < len; ++i )
		    l << qsToVariant( v.get( QString::number( i ) ), QVariant::Invalid );
		QVariant var = l;
		return var;
	    } else if ( type == QVariant::Map ) {
		QMap<QString, QVariant> m;
		QSPropertyMap *propMap = interpreter()->env()->arrayClass()->properties( &v );
		for ( QSPropertyMap::ConstIterator it = propMap->begin();
		      it != propMap->end(); ++it )
		    m.insert( it.key(), qsToVariant( (*it).object, QVariant::Invalid ) );
		QVariant var = m;
		return var;
	    } else {
		qWarning( "Can't convert Array to %s", QVariant::typeToName( type ) );
		break;
	    }
	} else {
	    //  qWarning( "qsToVariant: unhandled type %s",  v.typeName().latin1() );
	    return QVariant();
	}
    }
    return QVariant();
}

////////////////////////////////////////////////////////////////////

QSObject uObjectToQS( QUObject *o, const void *extra )
{
    if ( QUType::isEqual( o->type, &static_QUType_double ) )
	return QSNumber( static_QUType_double.get( o ) );
    else if ( QUType::isEqual( o->type, &static_QUType_int ) )
	return QSNumber( static_QUType_int.get( o ) );
    else if ( QUType::isEqual( o->type, &static_QUType_bool ) )
	return QSBoolean( static_QUType_bool.get( o ) );
    else if ( QUType::isEqual( o->type, &static_QUType_QString ) )
	return QSString( static_QUType_QString.get( o ) );
    else if ( QUType::isEqual( o->type, &static_QUType_charstar ) )
	return QSString( static_QUType_charstar.get( o ) );
    else if ( QUType::isEqual( o->type, &static_QUType_QVariant ) ) {
	QVariant var = static_QUType_QVariant.get( o );
	QuickScriptVariant v( var, 0 );
	if ( v.isNative() )
	    return v.toNative();
	return interpreter()->variantClass()->construct( var, 0 );
    } else if ( QUType::isEqual( o->type, &static_QUType_varptr ) ) {
	QVariant var;
	var.rawAccess( o->payload.ptr, (QVariant::Type)*(char*)extra, TRUE );
	QuickScriptVariant v( var, 0 );
	if ( v.isNative() )
	    return v.toNative();
	return interpreter()->variantClass()->construct( var, 0 );
    } else if ( QUType::isEqual( o->type, &static_QUType_ptr ) ) {
	if ( QMetaObject::hasMetaObject( (char*)extra ) )
	    return interpreter()->wrap( (QObject*)static_QUType_ptr.get( o ) );
	return interpreter()->pointerClass()->
	    wrapPointer((char*)extra, static_QUType_ptr.get( o ) );
    }
    return QSUndefined();
}

bool qsToUObject( QUObject *o, const QSObject &v, QUType *t, const void *extra )
{
    switch ( v.type() ) {
    case NullType:
    case UndefinedType:
    case BooleanType:
	static_QUType_bool.set( o, v.toBoolean() );
	break;
    case NumberType:
	static_QUType_double.set( o, v.toNumber() );
	break;
    case StringType:
	static_QUType_QString.set( o, v.toString() );
	break;
    default:
	if ( v.isA( interpreter()->pointClass() ) ) {
	    QVariant var = *interpreter()->pointClass()->point( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->sizeClass() ) ) {
	    QVariant var = *interpreter()->sizeClass()->size( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->rectClass() ) ) {
	    QVariant var = *interpreter()->rectClass()->rect( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->colorClass() ) ) {
	    QVariant var = *interpreter()->colorClass()->color( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->fontClass() ) ) {
	    QVariant var = *interpreter()->fontClass()->font( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->byteArrayClass() ) ) {
	    QVariant var = *interpreter()->byteArrayClass()->byteArray( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->pixmapClass() ) ) {
	    QVariant var = *interpreter()->pixmapClass()->pixmap( &v );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->variantClass() ) ) {
	    QVariant *var = interpreter()->variantClass()->variant( &v );
	    if ( QUType::isEqual( t, &static_QUType_QVariant ) ) {
		static_QUType_QVariant.set( o, *var );
	    } else if ( QUType::isEqual( t, &static_QUType_varptr ) ) {
		if ( (QVariant::Type)*(char*)extra != var->type() )
		    return FALSE;
		static_QUType_varptr.set( o, var->rawAccess() );
	    } else if ( QUType::isEqual( t, &static_QUType_ptr ) ) {
		static_QUType_ptr.set( o, var->rawAccess() );
	    } else {
		QSObject prim = v.toPrimitive( StringType );
		qsToUObject( o, prim, t );   // recursive call
	    }
	} else if ( v.isA( interpreter()->pointerClass() ) ) {
	    const char *ts = interpreter()->pointerClass()->pointerType( &v );
	    void *vptr = interpreter()->pointerClass()->pointer( &v );
	    if ( qstrcmp( ts, (const char*)extra ) != 0 ) {
		qWarning( "Parameter type mismatch. Needed %s, got %s",
			  (const char*)extra, ts );
		    return FALSE;
	    }
	    static_QUType_ptr.set( o, vptr );
	} else if ( v.isA( interpreter()->wrapperClass() ) ) {
	    QObject *qobj = interpreter()->
			    wrapperClass()->objectVector( &v ).at( 0 );
	    if ( qobj->inherits( (const char *)extra ) )
		static_QUType_ptr.set( o, qobj );
	    else
		return FALSE;
	} else if ( v.isA( "Date" ) ) {
	    QVariant var = qsToVariant( v, (QVariant::Type)*(char*)extra );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( interpreter()->pointClass() ) ) {
	    QVariant var( *interpreter()->pointClass()->point( &v ) );
	    static_QUType_QVariant.set( o, var );
	} else if ( v.isA( "Array" ) ) {
	    QVariant var = qsToVariant( v, (QVariant::Type)*(char*)extra );
	    static_QUType_varptr.set( o, var.rawAccess( 0, QVariant::Invalid, TRUE ) );
	} else {
	    return FALSE;
	}
    }

    return QUType::check( o, t );
}

////////////////////////////////////////////////////////////////////

QuickScriptReceiver::QuickScriptReceiver( QObject *o )
    : qobj( o ), handler( 0 )
{
}

QuickScriptReceiver::~QuickScriptReceiver()
{
    delete handler;
}

void QuickScriptReceiver::setEventHandler( int id, QObject *ctx, const QString &func, QSObject qsctx )
{
    if ( !handler )
	handler = new QuickScriptEventMap;
    if ( handler->find( id ) == handler->end() )
	QObject::connectInternal( qobj, id, this, QSLOT_CODE, id );
    (*handler)[id].eng = interpreter();
    (*handler)[id].targets << EventTarget::Target( ctx, qsctx, func );
}

void QuickScriptReceiver::removeEventHandler( int id, QObject *ctx, const QString &func, QSObject qsctx )
{
    if ( !handler )
	return;
    if ( handler->find( id ) == handler->end() )
	return;
    if ( (*handler)[id].targets.count() == 1 )
 	QObject::disconnectInternal( qobj, id, this, QSLOT_CODE, id );
    QValueList<EventTarget::Target>::Iterator it = (*handler)[id].targets.begin();
    while ( it != (*handler)[id].targets.end() ) {
	QValueList<EventTarget::Target>::Iterator it2 = it;
	++it;
	if ( (*it2).func == func &&
	     ( (*it2).ctx == ctx || (*it2).qsctx.equals( qsctx ) ) )
	    (*handler)[id].targets.remove( it2 );
    }
    if ( (*handler)[id].targets.count() == 0 )
	(*handler).remove( id );
}

bool QuickScriptReceiver::qt_invoke( int id, QUObject *o )
{
    const QMetaData* qmd = qobj->metaObject()->signal( id, TRUE );
    QuickMetaData md = QuickMetaData( qmd, id );
    QSList args;
    QuickScriptEventMap::Iterator it = handler->find( id );
    if ( it == handler->end() )
	return FALSE;
    const QUParameter *param = md.method->parameters;
    // 0th element contains return value
    for ( int i = 1; i < md.method->count+1; i++, param++ )
	args.append( uObjectToQS( &o[ i ], param->typeExtra ) );
    for ( QValueList<EventTarget::Target>::ConstIterator sit = (*it).targets.begin(); sit != (*it).targets.end(); ++sit ) {
	Q_ASSERT( (*it).eng == interpreter() );
	if ( (*sit).ctx )
	    (*it).eng->call( (*sit).ctx, (*sit).func, args );
	else
	    (*it).eng->call( (*sit).qsctx, (*sit).func, args );
    }

    return TRUE;
}

void stopAllTimers()
{
    TimerObject::stopTimers();
}

QSUserData::~QSUserData()
{
    if ( dat ) {
	QSWrapperShared *tmp = dat;
	tmp->invalidate(); // sets our dat to 0
	tmp->deref();
    }
}
