diff options
author | Ronan Collobert <ronan@collobert.com> | 2012-01-25 17:55:20 +0400 |
---|---|---|
committer | Ronan Collobert <ronan@collobert.com> | 2012-01-25 17:55:20 +0400 |
commit | 71678617672de3b91c62e25d9b2881977bac0b3b (patch) | |
tree | c4b6f1b8babfa147405aedef6feea0a733cae2d8 /qtlua |
initial revamp of torch7 tree
Diffstat (limited to 'qtlua')
-rw-r--r-- | qtlua/CMakeLists.txt | 60 | ||||
-rw-r--r-- | qtlua/QtLuaConfig.cmake.in | 9 | ||||
-rw-r--r-- | qtlua/qtluaconf.h.in | 32 | ||||
-rw-r--r-- | qtlua/qtluaengine.cpp | 3154 | ||||
-rw-r--r-- | qtlua/qtluaengine.h | 185 | ||||
-rw-r--r-- | qtlua/qtluautils.cpp | 712 | ||||
-rw-r--r-- | qtlua/qtluautils.h | 46 |
7 files changed, 4198 insertions, 0 deletions
diff --git a/qtlua/CMakeLists.txt b/qtlua/CMakeLists.txt new file mode 100644 index 0000000..f3a3cf2 --- /dev/null +++ b/qtlua/CMakeLists.txt @@ -0,0 +1,60 @@ +# -*- cmake -*- + +SET(QTLUA_DEFINITIONS) +SET(QTLUA_INCLUDE_DIR + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}") + +ADD_DEFINITIONS( ${QTLUA_DEFINITIONS} ${LUA_DEFINITIONS}) +INCLUDE_DIRECTORIES(${QTLUA_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) +SET(QT_DONT_USE_QTGUI 1) +INCLUDE(${QT_USE_FILE}) + +CONFIGURE_FILE("qtluaconf.h.in" "${CMAKE_CURRENT_BINARY_DIR}/qtluaconf.h") + +# --- compile library + +SET(qtlua_SRC "qtluautils.h" "qtluautils.cpp" "qtluaengine.h" "qtluaengine.cpp") +MACRO_QT4_AUTOGEN(qtlua_GEN ${qtlua_SRC}) + +ADD_LIBRARY(libqtlua SHARED ${qtlua_SRC} ${qtlua_GEN}) +TARGET_LINK_LIBRARIES(libqtlua ${LUA_LIBRARIES} ${QT_LIBRARIES}) +SET_TARGET_PROPERTIES(libqtlua PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME "qtlua" ) + +# --- install library and include files + +INSTALL(TARGETS libqtlua + RUNTIME DESTINATION ${Torch_INSTALL_BIN_SUBDIR} + LIBRARY DESTINATION ${Torch_INSTALL_LIB_SUBDIR} + ARCHIVE DESTINATION ${Torch_INSTALL_LIB_SUBDIR} ) + +INSTALL(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/qtluaengine.h + ${CMAKE_CURRENT_SOURCE_DIR}/qtluautils.h + ${CMAKE_CURRENT_BINARY_DIR}/qtluaconf.h + DESTINATION ${Torch_INSTALL_INCLUDE_SUBDIR}/qtlua +) + +# --- config for internal use + +SET(QTLUA_LIBRARIES "libqtlua") +SET(QTLUA_DEFINITIONS) +CONFIGURE_FILE(QtLuaConfig.cmake.in + "${Torch_BINARY_DIR}/cmake/QtLuaConfig.cmake") +SET(QtLua_DIR "${Torch_BINARY_DIR}/cmake" CACHE PATH + "Directory containing QtLuaConfig.cmake") +MARK_AS_ADVANCED(QtLua_DIR) + +# --- config for external use + +GET_TARGET_PROPERTY(QTLUA_OUTPUT_NAME libqtlua LOCATION) +GET_FILENAME_COMPONENT(QTLUA_OUTPUT_NAME ${QTLUA_OUTPUT_NAME} NAME) +SET(QTLUA_LIBRARIES "${Torch_INSTALL_LIB}/${QTLUA_OUTPUT_NAME}") +SET(QTLUA_INCLUDE_DIR "${Torch_INSTALL_INCLUDE}/qtlua") +CONFIGURE_FILE("QtLuaConfig.cmake.in" + "${Torch_BINARY_DIR}/cmake-external/QtLuaConfig.cmake") +INSTALL(FILES "${Torch_BINARY_DIR}/cmake-external/QtLuaConfig.cmake" + DESTINATION "${Torch_INSTALL_CMAKE_SUBDIR}") + diff --git a/qtlua/QtLuaConfig.cmake.in b/qtlua/QtLuaConfig.cmake.in new file mode 100644 index 0000000..87a0516 --- /dev/null +++ b/qtlua/QtLuaConfig.cmake.in @@ -0,0 +1,9 @@ +# This file was automatically generated. +# See @CMAKE_CURRENT_SOURCE_DIR@/CMakeLists.txt + +SET(QTLUA_FOUND ON) +SET(QTLUA_INCLUDE_DIR "@QTLUA_INCLUDE_DIR@") +SET(QTLUA_DEFINITIONS "@QTLUA_DEFINITIONS@") +SET(QTLUA_LIBRARIES "@QTLUA_LIBRARIES@") + + diff --git a/qtlua/qtluaconf.h.in b/qtlua/qtluaconf.h.in new file mode 100644 index 0000000..9884f4c --- /dev/null +++ b/qtlua/qtluaconf.h.in @@ -0,0 +1,32 @@ +// -*- C -*- + +#ifndef QTLUACONF_H +#define QTLUACONF_H + +#ifdef WIN32 +# ifdef libqtlua_EXPORTS +# define QTLUAAPI __declspec(dllexport) +# else +# define QTLUAAPI __declspec(dllimport) +# endif +#else +# define QTLUAAPI +#endif + +#ifdef __cplusplus +# define QTLUA_EXTERNC extern "C" +#else +# define QTLUA_EXTERNC extern +#endif + +#endif + + + + +/* ------------------------------------------------------------- + Local Variables: + c++-font-lock-extra-types: ( "\\sw+_t" "lua_[A-Z]\\sw*[a-z]\\sw*" ) + c-font-lock-extra-types: ( "\\sw+_t" "lua_[A-Z]\\sw*[a-z]\\sw*" ) + End: + ------------------------------------------------------------- */ diff --git a/qtlua/qtluaengine.cpp b/qtlua/qtluaengine.cpp new file mode 100644 index 0000000..26e7987 --- /dev/null +++ b/qtlua/qtluaengine.cpp @@ -0,0 +1,3154 @@ +// -*- C++ -*- + +#define QTLUAENGINE 1 + +#include "qtluautils.h" +#include "qtluaengine.h" +#include "lualib.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QEvent> +#include <QEventLoop> +#include <QFile> +#include <QList> +#include <QMap> +#include <QMetaMethod> +#include <QMetaObject> +#include <QMetaProperty> +#include <QMetaType> +#include <QMutex> +#include <QMutexLocker> +#include <QPointer> +#include <QSet> +#include <QTimer> +#include <QThread> +#include <QVector> +#include <QWaitCondition> + +#include <limits.h> + + + +// ======================================== +// Declaration + +static int luaQ_pcall(lua_State *L, + int na, int nr, int eh, + QObject *obj, bool async); + + + +/* miscellaneous information */ + +typedef QVector<int> IntVector; +typedef QVector<void*> PtrVector; +typedef QVector<QVariant> VarVector; + +struct QtLuaMethodInfo +{ + struct Detail { + int id; + PtrVector types; + }; + const QMetaObject *metaObject; + QVector<Detail> d; +}; + +Q_DECLARE_METATYPE(QtLuaMethodInfo) + +struct QtLuaPropertyInfo +{ + int id; + const QMetaObject *metaObject; + QMetaProperty metaProperty; +}; + +Q_DECLARE_METATYPE(QtLuaPropertyInfo) + +Q_DECLARE_METATYPE(QVariant) + + + +// ======================================== +// Registry keys + + +static const char *engineKey = "engine"; +static const char *signalKey = "signals"; +static const char *objectKey = "objects"; +static const char *metaKey = "metatables"; +static const char *qtKey = "qt"; + + +// Summary of registry entries: +// _R[engineKey] : pointer to QtLuaEngine::Private. +// _R[qtKey] : qt package. +// _R[qtKey].typename : class for metatype "typename". +// _R[qtKey].classname : class for metaobject "classname". +// _R[metaKey][typeid] : metatable for metatype (by typeid) +// _R[metaKey][metaobjectptr] : metatable for metaobject (by ptr) +// _R[objectKey][objectptr] : lua value for qobject (weak table) +// _R[signalKey][receiverptr] : closure for signal receiver. +// _R[metatable] : equal to qtKey if this is a qt object. + + +static void +luaQ_setup(lua_State *L, QtLuaEngine::Private *d) +{ + // metatypes + qRegisterMetaType<QVariant>("QVariant"); + qRegisterMetaType<QtLuaMethodInfo>("QtLuaMethodInfo"); + qRegisterMetaType<QtLuaPropertyInfo>("QtLuaPropertyInfo"); + qRegisterMetaType<QObjectPointer>("QObjectPointer"); + // engine + lua_pushlightuserdata(L, (void*)engineKey); + lua_pushlightuserdata(L, (void*)d); + lua_rawset(L, LUA_REGISTRYINDEX); + // metatables + lua_pushlightuserdata(L, (void*)metaKey); + lua_createtable(L, 0, 0); + lua_rawset(L, LUA_REGISTRYINDEX); + // signals + lua_pushlightuserdata(L, (void*)signalKey); + lua_createtable(L, 0, 0); + lua_rawset(L, LUA_REGISTRYINDEX); + // objects [weak table] + lua_pushlightuserdata(L, (void*)objectKey); + lua_createtable(L, 0, 0); + lua_createtable(L, 0, 1); + lua_pushliteral(L, "v"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); + // qt + lua_pushlightuserdata(L, (void*)qtKey); + lua_createtable(L, 0, 0); + lua_rawset(L, LUA_REGISTRYINDEX); + // package.preload["qt"] + lua_getfield(L, LUA_GLOBALSINDEX, "package"); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "preload"); + if (lua_istable(L, -1)) + { + lua_pushcfunction(L, luaopen_qt); + lua_setfield(L, -2, "qt"); + } + lua_pop(L, 1); + } + lua_pop(L, 1); +} + + +static QtLuaEngine::Private * +luaQ_private_noerr(lua_State *L) +{ + QtLuaEngine::Private *d = 0; + lua_pushlightuserdata(L, (void*)engineKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (lua_islightuserdata(L, -1)) + d = static_cast<QtLuaEngine::Private*>(lua_touserdata(L, -1)); + lua_pop(L, 1); + return d; +} + + +static QtLuaEngine::Private * +luaQ_private(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private_noerr(L); + if (! d) + luaL_error(L, "qtlua: not running inside a QtLuaEngine."); + return d; +} + + + + +// ======================================== +// QtLuaEngine::Global + +struct QtLuaEngine::Global { + QMutex mutex; + QMap<QByteArray,const QMetaObject*> knownMetaObjects; + void registerMetaObject(const QMetaObject *mo, bool super=true); + const QMetaObject *findMetaObject(QByteArray className); +}; + + +Q_GLOBAL_STATIC(QtLuaEngine::Global, qtLuaEngineGlobal); + + +void +QtLuaEngine::Global::registerMetaObject(const QMetaObject *mo, bool super) +{ + QMutexLocker locker(&mutex); + knownMetaObjects[mo->className()] = mo; + while (super && (mo = mo->superClass())) + knownMetaObjects[mo->className()] = mo; +} + + +const QMetaObject * +QtLuaEngine::Global::findMetaObject(QByteArray className) +{ + QMutexLocker locker(&mutex); + if (knownMetaObjects.contains(className)) + return knownMetaObjects[className]; + return 0; +} + + + +// ======================================== +// QtLuaEngine::Private basics + + +struct QtLuaQueuedSignal { + QPointer<QObject> sender; + QPointer<QtLuaEngine::Receiver> receiver; + void *delsignal; + VarVector args; +}; + + +struct QtLuaEngine::Protector : public QObject +{ + Q_OBJECT + Private *d; + QVariantList saved; + QMutex mutex; +public: + Protector(QtLuaEngine::Private *d) : d(d) { } + bool maybeProtect(const QVariant &var); + bool protect(const QVariant &var); + bool event(QEvent *e); +}; + + +struct QtLuaEngine::Private : public QObject +{ + Q_OBJECT +public: + Private(QtLuaEngine *parent); + ~Private(); + QThread *luaThread() const; + bool isObjectLuaOwned(QObject *obj); + void makeObjectLuaOwned(QObject *obj); + bool processQueuedSignals(QMutexLocker &locker); + void disconnectAllSignals(); + bool stopHelper(bool unwind); + bool resumeHelper(int retcode); + static void stopHook(lua_State *L, lua_Debug *ar); +public slots: + void objectDestroyed(QObject*); + void readySlot(); + void queueSlot(); + void stopSlot() {} + void emitStateChanged(State s) { emit stateChanged(s); } + void emitPauseSignal() { emit stateChanged(QtLuaEngine::Paused); } + void emitReadySignal() { emit readySignal(); } + void emitQueueSignal() { emit queueSignal(); } + void emitErrorMessage(QByteArray m) { emit errorMessage(m); } + void emitStopSignal() { emit stopSignal(); } +signals: + void errorMessage(QByteArray message); + void stateChanged(int s); + void readySignal(); + void queueSignal(); + void stopSignal(); +public: + QtLuaEngine *q; + lua_State *L; + // locking + QMutex mutex; + QWaitCondition condition; + int lockCount; + QThread *lockThread; + bool rflag; + // hopping + QWaitCondition hopCondition; + QEventLoop *hopLoop; + QEvent *hopEvent; + int hopNA; + int hopNR; + int hopEH; + // pausing + QEventLoop *pauseLoop; + QByteArray lastErrorMessage; + QList<QByteArray> lastErrorLocation; + bool printResults; + bool printErrors; + bool pauseOnError; + bool unwindStack; + bool resumeFlag; + bool errorHandlerFlag; + // debugging + lua_Debug *hookInfo; + lua_Hook hookFunction; + int hookMask; + int hookCount; + // protector + Protector *protector; + // maps + QMap<QByteArray,const QMetaObject*> knownMetaObjects; + QMap<QString,QObjectPointer> namedObjectsCache; + QSet<QObject*> namedObjects; + QSet<QObject*> luaOwnedObjects; + QList<QtLuaQueuedSignal> queuedSignals; +}; + + +QtLuaEngine::Private::Private(QtLuaEngine *parent) + : QObject(parent), + q(parent), + L(0), + lockCount(0), + lockThread(0), + rflag(true), + hopLoop(0), + hopEvent(0), + pauseLoop(0), + printResults(false), + printErrors(true), + pauseOnError(false), + unwindStack(false), + resumeFlag(false), + errorHandlerFlag(false), + hookInfo(0), + hookFunction(0), + hookMask(0), + hookCount(0), + protector(0) +{ + // setup paths +#if HAVE_LUA_EXECUTABLE_DIR + QString path = QCoreApplication::applicationFilePath(); + lua_executable_dir(QFile::encodeName(path).constData()); +#endif + // create lua interpreter + L = luaL_newstate(); + Q_ASSERT(L); + luaL_openlibs(L); + luaQ_setup(L, this); + // delayed connections + connect(this, SIGNAL(readySignal()), + this, SLOT(readySlot()), + Qt::QueuedConnection ); + connect(this, SIGNAL(queueSignal()), + this, SLOT(queueSlot()), + Qt::QueuedConnection ); + connect(this, SIGNAL(stopSignal()), + this, SLOT(stopSlot()), + Qt::QueuedConnection ); + connect(this, SIGNAL(stateChanged(int)), + q, SIGNAL(stateChanged(int))); + connect(this, SIGNAL(errorMessage(QByteArray)), + q, SIGNAL(errorMessage(QByteArray))); +} + + +QtLuaEngine::Private::~Private() +{ + // close interpreter + lua_pushlightuserdata(L, (void*)engineKey); + lua_pushnil(L); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_close(L); + L = 0; + // destroy protector + if (protector) + protector->deleteLater(); + protector = 0; +} + + +QThread * +QtLuaEngine::Private::luaThread() const +{ + if (lockThread) + return lockThread; + return thread(); +} + + +void +QtLuaEngine::Private::objectDestroyed(QObject *obj) +{ + QMutexLocker locker(&mutex); + if (luaOwnedObjects.contains(obj)) + luaOwnedObjects.remove(obj); + if (namedObjects.contains(obj)) + namedObjects.remove(obj); +} + + +bool +QtLuaEngine::Private::isObjectLuaOwned(QObject *obj) +{ + QMutexLocker locker(&mutex); + return luaOwnedObjects.contains(obj); +} + + +void +QtLuaEngine::Private::makeObjectLuaOwned(QObject *obj) +{ + if (obj) + { + connect(obj, SIGNAL(destroyed(QObject*)), + this, SLOT(objectDestroyed(QObject*)), + Qt::DirectConnection ); + QMutexLocker locker(&mutex); + luaOwnedObjects += obj; + } +} + + +static void +queue_sub(QtLuaEngine::Private *d, QMutexLocker &locker) +{ + QThread *mythread = QThread::currentThread(); + Q_ASSERT(mythread == d->thread()); + Q_ASSERT(!d->lockCount && !d->hopEvent && !d->hopLoop); + d->lockCount = 1; + d->lockThread = mythread; + d->processQueuedSignals(locker); + if (! d->queuedSignals.isEmpty()) + d->emitQueueSignal(); + d->lockCount = 0; + d->lockThread = 0; + d->condition.wakeOne(); +} + + +void +QtLuaEngine::Private::readySlot() +{ + QMutexLocker locker(&mutex); + if (!lockCount && !hopLoop && !hopEvent && !pauseLoop) + queue_sub(this, locker); + bool stateChanged = rflag; + hookInfo = 0; + unwindStack = false; + rflag = false; + if (lua_gethook(L) == stopHook) + lua_sethook(L, hookFunction, hookMask, hookCount); + locker.unlock(); + if (stateChanged) + emit q->stateChanged(QtLuaEngine::Ready); +} + + +void +QtLuaEngine::Private::queueSlot() +{ + QMutexLocker locker(&mutex); + if (pauseLoop && ! hookInfo) + { + Q_ASSERT(rflag); + QEventLoop *savedLoop = pauseLoop; + lua_Debug *savedInfo = hookInfo; + pauseLoop = 0; + hookInfo = 0; + rflag = false; + resumeFlag = false; + queue_sub(this, locker); + bool stateChanged = rflag; + pauseLoop = savedLoop; + hookInfo = savedInfo; + rflag = true; + locker.unlock(); + if (stateChanged) + emit q->stateChanged(QtLuaEngine::Paused); + if (pauseLoop && unwindStack) + pauseLoop->exit(1); + else if (pauseLoop && resumeFlag) + pauseLoop->exit(0); + } + else if (!lockCount && !lockThread && !hopEvent && !pauseLoop) + { + queue_sub(this, locker); + bool stateChanged = rflag; + hookInfo = 0; + unwindStack = false; + rflag = false; + if (lua_gethook(L) == stopHook) + lua_sethook(L, hookFunction, hookMask, hookCount); + locker.unlock(); + if (stateChanged) + emit q->stateChanged(QtLuaEngine::Ready); + } +} + + + + +// ======================================== +// QtLuaEngine state + + + +/*! Returns the engine state. */ + +QtLuaEngine::State +QtLuaEngine::state() const +{ + QMutexLocker locker(&d->mutex); + if (d->pauseLoop) + return Paused; + else if (d->rflag) + return Running; + else + return Ready; +} + +/*! \fn QtLuaEngine::isReady() + Returns \a true if the engine is in ready state. */ + +/*! \fn QtLuaEngine::isRunning() + Returns \a true if the engine is in running state. */ + +/*! \fn QtLuaEngine::isPaused() + Returns \a true if the engine is in paused state. */ + + +/*! Returns true if the engine is paused because an error has occured */ + +bool +QtLuaEngine::isPausedOnError() const +{ + QMutexLocker locker(&d->mutex); + if (d->pauseLoop && d->errorHandlerFlag) + return true; + return false; +} + + +/*! \signal stateChanged(State state) + Posted when the engine state changes. + When this message is received, + the engine state might have changed again! */ + + +/*! \signal errorMessage(QByteArray message) + Posted when a lua error is signalled. */ + + + +// ======================================== +// QtLuaLocker + + +/*! \class QtLuaLocker + This class safely locks a \a QtLuaEngine. + The constructor acquires the lock + and the destructor releases the lock. + The \a lua_State can then be safely + accessed by casting an instance of this class. */ + + +QtLuaLocker::~QtLuaLocker() +{ + if (engine && count) + { + QtLuaEngine::Private *d = engine->d; + d->mutex.lock(); + count = d->lockCount - 1; + if (! count) + { + if (d->hopEvent) + { + d->hopCondition.wakeOne(); // mostly for async eval + } + else + { + d->lockThread = 0; + d->emitReadySignal(); + d->condition.wakeOne(); + } + } + d->lockCount = count; + d->mutex.unlock(); + } +} + + +/*! Constructor. + This constructor acquires a lock, + waiting as long as necessary. */ + +QtLuaLocker::QtLuaLocker(QtLuaEngine *engine) + : engine(engine), count(0) +{ + QtLuaEngine::Private *d = engine->d; + QThread *mythread = QThread::currentThread(); + QMutexLocker locker(&d->mutex); + for(;;) + { + if (d->lockCount > 0 && d->lockThread == mythread) + break; + if (d->lockCount == 0 && d->lockThread == 0) + break; + d->condition.wait(&d->mutex); + } + d->lockCount += 1; + d->lockThread = mythread; + count = d->lockCount; +} + + + +/*! Alternate constructor. + This constructor attempts to acquire a lock + waiting at most \a timeOut milliseconds. + To see if the lock has been acquired, + use the \a lua_State* conversion operator + or use the \a isReady() function. */ + +QtLuaLocker::QtLuaLocker(QtLuaEngine *engine, int timeOut) + : engine(engine), count(0) +{ + QtLuaEngine::Private *d = engine->d; + QThread *mythread = QThread::currentThread(); + QMutexLocker locker(&d->mutex); + for(;;) + { + if (d->lockCount > 0 && d->lockThread == mythread) + break; + if (d->lockCount == 0 && d->lockThread == 0) + break; + if (! d->condition.wait(&d->mutex, timeOut)) + return; + } + d->lockCount += 1; + d->lockThread = mythread; + count = d->lockCount; +} + + +/*! \fn bool isReady() + Returns true if the locking operation was + successful and the interpreter is in ready state. + Note that locking and state are distinct concepts. + It is possible to lock a running interpreter + while it is waiting for other events. + The \a eval and \a evaluate functions use this test + to decide whether to run a command. */ + + +/*! \fn operator lua_State* + Accesses the \a lua_State underlying the locked engine. + This provides the only safe way to access + the \a lua_State variable. */ + + +/*! Calling this function after a successful lock + causes the engine to transition to state \a Running. + The engine will return to state \a Ready after + the destruction of the last \a QtLuaLocker object + and the execution of the command queue. + Temporary releasing the lock with \a unlock() + keeps the engine in state \a Running. */ + +void +QtLuaLocker::setRunning() +{ + bool stateChanged = false; + QtLuaEngine::Private *d = engine->d; + if (count > 0) + { + QMutexLocker locker(&d->mutex); + stateChanged = !d->rflag; + d->rflag = true; + } + if (stateChanged) + d->emitStateChanged(QtLuaEngine::Running); +} + + + + + + + +// ======================================== +// QtLuaEngine basics + + +/*! \class QtLuaEngine + Class \a QtLuaEngine represents a Lua interpreter. + This object can be used to add a Lua interpreter + to any Qt application with capabilities + comparable to those of the QtScript language + and additional support for multi-threaded execution. + + Instances of this class can be in one of three state. + State \a QtLuaEngine::Ready indicates that the interpreter + is ready to accept new Lua commands. + State \a QtLuaEngine::Running indicates that the interpreter + is currently executing a Lua program. + State \a QtLuaEngine::Paused indicates that the interpreter + was suspended while executing a Lua program. One can then + use the Lua debug library to investigage the Lua state. + + Class \a QtLuaEngine provides member functions to + submit Lua strings to the interpreter and to collect + the evaluation results. If these functions are invoked + from the thread owning the Lua engine object, + the Lua code is executed right away. + Otherwise a thread hopping operation with \a luaQ_pcall + ensures that the execution of a Lua program happens + in the thread owning the Lua engine object. */ + + +QtLuaEngine::~QtLuaEngine() +{ + Q_ASSERT(QThread::currentThread() == thread()); + // make as silent as possible + setPrintResults(false); + setPauseOnError(false); + disconnect(d, 0, this, 0); + d->disconnectAllSignals(); + // stop lua + QMutexLocker locker(&d->mutex); + while (d->lockCount || d->lockThread || d->hopEvent) + { + locker.unlock(); + resume(true); + stop(true); + QEventLoop loop; + loop.processEvents(QEventLoop::ExcludeUserInputEvents, 100); + locker.relock(); + d->unwindStack = true; + } + // delete children and owned objects + QObject *obj; + QList<QObjectPointer> family; + foreach(obj, d->luaOwnedObjects) + family << obj; + foreach(obj, children()) + family << obj; + d->luaOwnedObjects.clear(); + foreach(QObjectPointer objptr, family) + if ((obj = objptr) && (obj != d)) + obj->deleteLater(); + // disconnect protector + if (d->protector) + QCoreApplication::instance()->removeEventFilter(d->protector); +} + + +/*! Basic constructor. */ + +QtLuaEngine::QtLuaEngine(QObject *parent) + : QObject(parent), + d(new Private(this)), + L(d->L) +{ + d->protector = new QtLuaEngine::Protector(d); + d->protector->moveToThread(QCoreApplication::instance()->thread()); + QCoreApplication::instance()->installEventFilter(d->protector); +} + + +/*! Registering a metaobject allows the engine + to recognize qobject classes by name in method + and signal arguments. This happens automatically + when a qobject is translated into a lua userdata, + or, more generally, whenever \a luaQ_pushmeta is called. + In rare occasions, it may be necessary to manually + register a meta object, for instance when a scriptable + method returns a qobject whose class has not been previously + registered and is not a superclass of the current class, + or when one connects a signal whose arguments are + qobjects whose class has not been previously registered. */ + +void +QtLuaEngine::registerMetaObject(const QMetaObject *mo) +{ + qtLuaEngineGlobal()->registerMetaObject(mo); +} + + +/*! Make a \a QObject accessible to the interpreter by name. + Use the Qt object name when string \a name is not provided. + The lua engine keeps tracking the object when its name + is reset or changed. */ + +void +QtLuaEngine::nameObject(QObject *object, QString name) +{ + if (object) + { + if (! name.isEmpty()) + object->setObjectName(name); + name = object->objectName(); + QMutexLocker locker(&d->mutex); + d->namedObjects += object; + if (! name.isEmpty()) + d->namedObjectsCache[name] = object; + connect(object, SIGNAL(destroyed(QObject*)), + d, SLOT(objectDestroyed(QObject*)), + Qt::DirectConnection ); + } +} + + +/*! Returns a \a QObject by name. */ + +QObject * +QtLuaEngine::namedObject(QString name) +{ + QObject *obj; + QMutexLocker locker(&d->mutex); + if (d->namedObjectsCache.contains(name)) + { + obj = d->namedObjectsCache[name]; + if (obj && obj->objectName() == name) + return obj; + d->namedObjectsCache.remove(name); + } + foreach(obj, d->namedObjects) + if (obj->objectName() == name) + { + d->namedObjectsCache[name] = obj; + return obj; + } + return 0; +} + + +/*! Return the list of all named objects. */ + +QList<QObjectPointer> +QtLuaEngine::allNamedObjects() +{ + QObject *obj; + QList<QObjectPointer> list; + foreach(obj, d->namedObjects) + list += obj; + return list; +} + + +/*! \property QtLuaEngine::lastErrorMessage + Contains the last error message + reported with signal \a errorMessage. */ + +QByteArray +QtLuaEngine::lastErrorMessage() const +{ + return d->lastErrorMessage; +} + + +/*! \property QtLuaEngine::lastErrorLocation + Contains the location associated with the + last error message reported with signal \a errorMessage. + When the location corresponds to a file, + this string has the format "@filename:linenumber". */ + +QStringList +QtLuaEngine::lastErrorLocation() const +{ + QStringList list; + foreach(QByteArray b, d->lastErrorLocation) + list << QString::fromLocal8Bit(b); + return list; +} + + + + +/*! \property QtLuaEngine::printResults + Indicates whether the results of an \a eval() + must be printed on the standard output. */ + +bool +QtLuaEngine::printResults() const +{ + return d->printResults; +} + + +void +QtLuaEngine::setPrintResults(bool b) +{ + d->printResults = b; +} + + +/*! \property QtLuaEngine::printErrors + Indicates whether error messages + must be printed on the standard output. */ + +bool +QtLuaEngine::printErrors() const +{ + return d->printErrors; +} + + +void +QtLuaEngine::setPrintErrors(bool b) +{ + d->printErrors = b; +} + + + +/*! \property QtLuaEngine::pauseOnError + Indicates whether the default error handler + should pause execution when an error occurs + as if \a pause() had been called from + the error handler. */ + +bool +QtLuaEngine::pauseOnError() const +{ + return d->pauseOnError; +} + + +void +QtLuaEngine::setPauseOnError(bool b) +{ + d->pauseOnError = b; +} + + +/*! \property QtLuaEngine::runSignalHandlers + This property is true when the Lua interpreter + honors the signal handler invokations immediately + instead of queuing them for further processing. */ + +bool +QtLuaEngine::runSignalHandlers() const +{ + QMutexLocker locker(&d->mutex); + if (! d->rflag) + return true; + if (d->pauseLoop && ! d->hookInfo) + return true; + return false; +} + + + + +// ======================================== +// Stopping, Resuming, Evaluating + + +static bool +lua_pause_sub(lua_State *L, QtLuaEngine::Private *d, QMutexLocker &locker) +{ + // must be called with a message on the stack + if (!d->pauseLoop && d->rflag && !d->unwindStack) + { + int savedLockCount = d->lockCount; + QThread *savedLockThread = d->lockThread; + QEventLoop loop; + d->pauseLoop = &loop; + d->lockCount = 0; + d->lockThread = 0; + d->condition.wakeOne(); + locker.unlock(); + // make sure we enter the loop before signalling. + QTimer::singleShot(0, d, SLOT(emitPauseSignal())); + if (loop.exec()) + d->unwindStack = true; + locker.relock(); + d->lockThread = savedLockThread; + d->lockCount = savedLockCount; + d->pauseLoop = 0; + return true; + } + return false; +} + + +void +QtLuaEngine::Private::stopHook(lua_State *L, lua_Debug *ar) +{ + QtLuaEngine::Private *d = luaQ_private_noerr(L); + lua_sethook(L, 0, 0, 0); + lua_pushliteral(L, "stop"); + luaQ_tracebackskip(L, 1); + bool stateChanged = false; + if (d) + { + QMutexLocker locker(&d->mutex); + lua_Debug *savedInfo = d->hookInfo; + d->hookInfo = ar; + lua_sethook(L, d->hookFunction, d->hookMask, d->hookCount); + if (! d->unwindStack) + stateChanged = lua_pause_sub(L, d, locker); + d->hookInfo = savedInfo; + } + if (d && stateChanged) + d->emitStateChanged(QtLuaEngine::Running); + lua_pop(L, 1); + if (d && !d->unwindStack) + return; + lua_pushliteral(L, "stop"); + lua_error(L); +} + + +static QList<QByteArray> +find_error_location(lua_State *L, QByteArray &message) +{ + QByteArray loc; + QList<QByteArray> list; + // parse locations in message + const char *m = message.constData(); + const char *s = m; + for(const char *s = m; *s && *s != '\n'; s++) + { + // search ":[0-9]+: +" + if (*s != ':') + continue; + char *e; + QByteArray location(m, s-m); + int lineno = (int)strtol(++s, &e, 10); + if (lineno <= 0 || e <= s || *e != ':') + continue; + s = e; + while (s[1] && s[1] == ' ') + s += 1; + m = s + 1; + // append + if (location.startsWith("[string \"") && location.endsWith("\"]")) + location = location.mid(9, location.length()-11); + else + location = "@" + location; + loc = location + ":" + QByteArray::number(lineno); + if (list.isEmpty() || list.first() != loc) + list.prepend(loc); + } + message = QByteArray(m); + // parse locations in top ten stack elements + int maxlevel = 8; + int level = 0; + lua_Debug ar; + while (level < maxlevel && lua_getstack(L, level++, &ar)) + { + lua_getinfo(L, "Snl", &ar); + if (ar.currentline > 0) + { + loc = QByteArray(ar.source) + ":" + + QByteArray::number(ar.currentline); + if (list.isEmpty() || list.last() != loc) + list.append(loc); + } + } + return list; +} + + +static int +lua_error_handler(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private_noerr(L); + const char *m = lua_tostring(L, -1); + if (m && strstr(m, "\nstack traceback:\n\t")) + return 1; // hack alert (see lua/src/ldblib.c) + luaQ_tracebackskip(L, 1); + QByteArray message = lua_tostring(L, -1); + QList<QByteArray> location = find_error_location(L, message); + bool stateChanged = false; + if (d) + { + d->lastErrorLocation = location; + d->lastErrorMessage = message; + d->emitErrorMessage(d->lastErrorMessage); + QMutexLocker locker(&d->mutex); + d->errorHandlerFlag = true; + if (d->pauseOnError && !d->unwindStack) + stateChanged = lua_pause_sub(L, d, locker); + d->errorHandlerFlag = false; + } + if (stateChanged) + d->emitStateChanged(QtLuaEngine::Running); + return 1; +} + + + +bool +QtLuaEngine::Private::stopHelper(bool unwind) +{ + unwindStack |= unwind; + lua_Hook hf = lua_gethook(L); + if (hf != stopHook) + { + hookFunction = lua_gethook(L); + hookMask = lua_gethookmask(L); + hookCount = lua_gethookcount(L); + } + lua_sethook(L, stopHook, LUA_MASKCOUNT|LUA_MASKRET, 1); + emitStopSignal(); // just to make sure luaQ_doevents() is stopped! + return true; +} + +bool +QtLuaEngine::Private::resumeHelper(int retcode) +{ + if (pauseLoop) + pauseLoop->exit(retcode); + return true; +} + + +/*! Stops the lua execution as soon as practicable. + If flag \a nopause if false, the interpreter + will transition to the paused state until someone + calls \a resume(). Otherwise the interpreter stops + executing, unwinds the stack, and returns to ready state. */ + +bool +QtLuaEngine::stop(bool nopause) +{ + QMutexLocker locker(&d->mutex); + if (d->pauseLoop && nopause) + return d->resumeHelper(1); + if (d->rflag && !d->pauseLoop) + return d->stopHelper(nopause); + return false; +} + + +/*! Resume execution after a \a pause(). + Returns \a false when called when the engine is not in paused state. + When argument \a nocontinue is true, the interpreter + stops executing, unwinds the stack, and returns to ready state. */ + +bool +QtLuaEngine::resume(bool nocontinue) +{ + QMutexLocker locker(&d->mutex); + if (d->pauseLoop) + return d->resumeHelper(nocontinue ? 1 : 0); + if (d->rflag && !d->pauseLoop && nocontinue) + return d->stopHelper(true); + return false; +} + + +static int +lua_eval_func(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private_noerr(L); + d->errorHandlerFlag = false; + lua_pushcfunction(L, lua_error_handler); + int error = luaL_loadstring(L, lua_tostring(L, 1)); + if (! error) + error = lua_pcall(L, 0, LUA_MULTRET, -2); + if (!d || d->printResults || (error && d->printErrors)) + luaQ_print(L, lua_gettop(L) - 2); + if (error) + lua_error(L); + return lua_gettop(L) - 2; +} + + +/*! Evaluate the expression in string \a s. + This function returns \a true if the evaluation + was performed without error. It immediately + returns \a false if the \a QtLuaEngine instance + state is not "ready" or when one requests + an asynchronous call from the engine thread. + Evaluation takes place in the thread + owning the \a QtLuaEngine instance. + Synchronous calls are performed by setting + flag \a async is \a false. The function waits + until the evaluation terminates regardless of + the thread from which it is called. + Asynchronous calls are performed by setting + flag \a async to \a true and calling this + function from a thread other than the thread + owning the \a QtLuaEngine instance. */ + +bool +QtLuaEngine::eval(QByteArray s, bool async) +{ + QtLuaLocker lua(this); + if (! lua.isReady()) + return false; + if (async && QThread::currentThread() == thread()) + return false; + lua_settop(L, 0); + lua_pushcfunction(L, lua_eval_func); + lua_pushstring(L, s.constData()); + int status = luaQ_pcall(L, 1, 0, 0, this, async); + return (status == 0); +} + + +/*! \overload */ + +bool +QtLuaEngine::eval(QString s, bool async) +{ + return eval(s.toLocal8Bit(), async); +} + + +/*! Synchronously evaluate string \a s and returns the result. + This function returns an empty \a QVariantList if + called when the engine is not in ready state. + If an error occurs during evaluation, it returns a list + whose first element is \a QVariant(false) and whose + second element is the error message. + Otherwise is returns a list whose first element is + \a QVariant(true) and whose remaining elements + are the evaluation results. +*/ + +QVariantList +QtLuaEngine::evaluate(QByteArray s) +{ + QVariantList results; + QtLuaLocker lua(this); + if (! lua.isReady()) + return results; + lua_settop(L, 0); + lua_pushcfunction(L, lua_eval_func); + lua_pushstring(L, s.constData()); + int status = luaQ_pcall(L, 1, LUA_MULTRET, 0, this); + if (status) + results << QVariant(false); + else + results << QVariant(true); + for (int i = 1; i <= lua_gettop(L); i++) + results << luaQ_toqvariant(L, i); + return results; +} + + +/*! \overload */ + +QVariantList +QtLuaEngine::evaluate(QString s) +{ + return evaluate(s.toLocal8Bit()); +} + + + + +// ======================================== +// Calls + + +struct QtLuaEngine::Catcher : public QObject +{ + Q_OBJECT +public: + QtLuaEngine::Private *d; + Catcher(QtLuaEngine::Private *d) : d(d) { } + virtual bool event(QEvent *e); +public slots: + void destroy() { delete this; } +}; + + +bool +QtLuaEngine::Catcher::event(QEvent *e) +{ + if (e->type() != QEvent::User) + return false; + QMutexLocker locker(&d->mutex); + Q_ASSERT(e == d->hopEvent); + while (d->lockCount > 0) + d->hopCondition.wait(&d->mutex); + // lock + d->lockThread = QThread::currentThread(); + d->lockCount = 1; + d->hopEvent = 0; + bool rflag = d->rflag; + int stacktop = lua_gettop(d->L); + int status = LUA_ERRRUN; + if (d->unwindStack) + { + lua_pushliteral(d->L, "stop (unwinding stack)"); + } + else try + { + d->rflag = true; + locker.unlock(); + if (! rflag) + d->emitStateChanged(QtLuaEngine::Running); + status = lua_pcall(d->L, d->hopNA, d->hopNR, d->hopEH); + locker.relock(); + } + catch(...) + { + lua_settop(d->L, stacktop); + lua_pushliteral(d->L, "uncaught c++ exception"); + } + d->lockCount = 0; + if (d->hopLoop) + d->hopLoop->exit(status); + else + d->emitReadySignal(); + // We use a timer because calling 'delete this' + // in an event handler is not supported and + // because 'deleteLater' waits until the + // current eventloop returns... + QTimer::singleShot(0, this, SLOT(destroy())); + return true; +} + + +struct QtLuaEngine::Unlocker : public QObject +{ + Q_OBJECT +public: + QtLuaEngine::Private *d; + Unlocker(QtLuaEngine::Private *d) : d(d) {} + virtual bool event(QEvent *e); +}; + + +bool +QtLuaEngine::Unlocker::event(QEvent *e) +{ + if (e->type() != QEvent::User) + return false; + d->mutex.lock(); + d->lockCount = 0; + d->hopCondition.wakeOne(); + d->mutex.unlock(); + return true; +} + + +static int +luaQ_pcall(lua_State *L, int na, int nr, int eh, QObject *obj, bool async) +{ + int status; + // obtain lua engine + QtLuaEngine::Private *d = luaQ_private(L); + d->mutex.lock(); + if (! obj) + obj = QCoreApplication::instance(); + if (obj->thread() == QThread::currentThread()) + { + bool rflag = d->rflag; + d->rflag = true; + d->mutex.unlock(); + if (! rflag) + d->emitStateChanged(QtLuaEngine::Running); + status = lua_pcall(L, na, nr, eh); + } + else + { + QEvent *event = new QEvent(QEvent::User); + d->hopEvent = event; + d->hopNA = na; + d->hopNR = nr; + d->hopEH = eh; + QThread *thread = obj->thread(); + QtLuaEngine::Catcher *catcher = new QtLuaEngine::Catcher(d); + catcher->moveToThread(thread); + QCoreApplication::postEvent(catcher, event); + if (async) + { + // This should only be used for async eval. + // Freeing the last lua locker will wake + // the catcher waiting on hopCondition + Q_ASSERT(! d->rflag); + d->mutex.unlock(); + return 0; + } + else + { + QEventLoop *savedHopLoop = d->hopLoop; + int savedLockCount = d->lockCount; + QThread *savedLockThread = d->lockThread; + QEventLoop loop; + d->hopLoop = &loop; + // The catcher will soon get the hopEvent message + // and run lua_pcall() in the receiving thread. + // Then it calls exit() causing us to leave the + // event loop and resume execution in this thread. + // For this to happen we need to unlock the engine + // and signal hopCondition. But we cannot do it right now + // because the other thread might complete even before + // we call loop.exec(). Posting a message to the Unlocker + // object ensures that unlock happens after exec() has started. + // This is not very high-performance :-(. + QtLuaEngine::Unlocker unlocker(d); + QEvent *unlockEvent = new QEvent(QEvent::User); + QCoreApplication::postEvent(&unlocker, unlockEvent); + d->mutex.unlock(); + status = loop.exec(); + d->mutex.lock(); + d->lockCount = savedLockCount; + d->lockThread = savedLockThread; + d->hopLoop = savedHopLoop; + d->mutex.unlock(); + } + } + return status; +} + + +/*! Thread hopping version of \a lua_pcall(). + This function is similar to \a lua_pcall() but + arranges the execution to happen in the + thread of the Qt object \a obj. + This only works if the target thread is running an event loop. + The current thread then runs an event loop until + being notified of the call results. + When \a obj is null, the application object + is assumed (ensuring the code runs in the gui thread). */ + +int +luaQ_pcall(lua_State *L, int na, int nr, int eh, QObject *obj) +{ + return luaQ_pcall(L, na, nr, eh, obj, false); +} + + +/*! Convenience function. + Same as "luaQ_pcall(L,na,nr,0,obj) || lua_error(L)". + Unlike \a lua_call() this function calls + the function with the default error handler + instead of the current error handler. */ + +void +luaQ_call(lua_State *L, int na, int nr, QObject *obj) +{ + int base = lua_gettop(L) - na; + Q_ASSERT(base > 0); + lua_pushcfunction(L, lua_error_handler); + lua_insert(L, base); + int status = luaQ_pcall(L, na, nr, base, obj); + lua_remove(L, base); + if (status) + lua_error(L); +} + + + + +static int +call_in_obj_thread(lua_State *L) +{ + int narg = lua_gettop(L); + // object + lua_pushvalue(L, lua_upvalueindex(2)); + QObject *obj = luaQ_toqobject(L, -1); + lua_pop(L, 1); + // function + lua_pushvalue(L, lua_upvalueindex(1)); + Q_ASSERT(lua_isfunction(L, -1)); + lua_insert(L, 1); // function + // call + luaQ_call(L, narg, LUA_MULTRET, obj); + return lua_gettop(L); +} + + +static int +call_in_arg_thread(lua_State *L) +{ + int narg = lua_gettop(L); + // object + QObject *obj = luaQ_toqobject(L, 1); + if (! obj) + luaL_typerror(L, 1, "qobject"); + // function + lua_pushvalue(L, lua_upvalueindex(1)); + Q_ASSERT(lua_isfunction(L, -1)); + lua_insert(L, 1); + // call + luaQ_call(L, narg, LUA_MULTRET, obj); + return lua_gettop(L); +} + + + +/*! Register functions to be called in the thread of object \a obj. + If argument \a obj is null, the functions will be called + in the thread of their first argument (which must be a qobject). + This is handy to define methods in metaclasses. */ + +void +luaQ_register(lua_State *L, const luaL_Reg *l, QObject *obj) +{ + while (l->name) + { + lua_pushcfunction(L, l->func); + if (obj) { + luaQ_pushqt(L, obj); + lua_pushcclosure(L, call_in_obj_thread, 2); + } else + lua_pushcclosure(L, call_in_arg_thread, 1); + lua_setfield(L, -2, l->name); + l += 1; + } +} + + + + + +// ======================================== +// Bindings + + +/*! Returns the engine for the current interpreter. */ + +QtLuaEngine * +luaQ_engine(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private(L); + return d->q; +} + + +/*! Pushes the qt package table on the stack. */ + +void +luaQ_pushqt(lua_State *L) +{ + lua_pushlightuserdata(L, (void*)qtKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); +} + + +/* conversions and tests */ + +static QVariant * +luaQ_toqvariantp(lua_State *L, int index) +{ + QVariant *vp = 0; + if (lua_isuserdata(L, index) && + lua_getmetatable(L, index)) + { + lua_rawget(L, LUA_REGISTRYINDEX); + void *v = lua_touserdata(L, -1); + lua_pop(L, 1); + if (v == (void*)qtKey) + vp = static_cast<QVariant*>(lua_touserdata(L, index)); + } + return vp; +} + + +static inline bool +qvariant_has_object_type(const QVariant *vp) +{ + int type = vp->userType(); + return (type == qMetaTypeId<QObjectPointer>() || + type == QMetaType::QObjectStar || + type == QMetaType::QWidgetStar ); +} + + +static inline QObject * +qvariant_to_object(const QVariant *vp) +{ + int type = vp->userType(); + if (type == qMetaTypeId<QObjectPointer>()) + return *static_cast<QObjectPointer const *>(vp->constData()); + if (type == QMetaType::QObjectStar) + return *static_cast<QObject * const *>(vp->constData()); + return 0; +} + + +static inline QObject * +qvariant_to_object(const QVariant *vp, const QMetaObject *mo) +{ + QObject *obj = qvariant_to_object(vp); + if (obj && mo) + { + const QMetaObject *m = obj->metaObject(); + while (m && m != mo) + m = m->superClass(); + if (m != mo) + obj = 0; + } + return obj; +} + + +/*! Extract \a QVariant from the lua value located + at position \a index in the stack. + Standard lua types are converted to \a QVariant + as needed. */ + +QVariant +luaQ_toqvariant(lua_State *L, int index, int type) +{ + QVariant v; + switch (lua_type(L, index)) + { + case LUA_TBOOLEAN: + v = QVariant((bool)lua_toboolean(L, index)); + break; + case LUA_TNUMBER: + v = QVariant((double)lua_tonumber(L, index)); + break; + case LUA_TUSERDATA: { + const QVariant *vp = 0; + if (! (vp = luaQ_toqvariantp(L, index))) + break; + if (qvariant_has_object_type(vp) && !qvariant_to_object(vp)) + break; + v = *vp; + break; } + case LUA_TSTRING: { + size_t l; + const char *s = lua_tolstring(L, index, &l); + v = QVariant(QByteArray(s, l)); + break; } + default: + break; + } + if (type) + { + int vtype = v.userType(); + if (type == vtype) + return v; + else if (type == QVariant::ByteArray && vtype == QVariant::String) + v = v.toString().toLocal8Bit(); + else if (type == QVariant::String && vtype == QVariant::ByteArray) + v = QString::fromLocal8Bit(v.toByteArray().constData()); + else if (! v.convert(QVariant::Type(type))) + v = QVariant(); + } + return v; +} + + +/*! Returns true if the lua value at position \a index + points to a \a QObject that inherits from the class + identified by metaobject \a mo. */ + +QObject* +luaQ_toqobject(lua_State *L, int index, const QMetaObject *mo) +{ + const QVariant *vp = luaQ_toqvariantp(L, index); + return (vp) ? qvariant_to_object(vp, mo) : 0; +} + + + +/* This part of the protector makes sure that + certain implicit shared objects are + destroyed in the gui thread instead + of the thread that destroys the variant. + Yes this is tricky. */ + +bool +QtLuaEngine::Protector::maybeProtect(const QVariant &var) +{ + if (QThread::currentThread() == QCoreApplication::instance()->thread()) + return false; + int type = var.userType(); + switch(type) + { + case QMetaType::QPixmap: + case QMetaType::QBrush: + case QMetaType::QPen: + return protect(var); + default: + return false; + } +} + +bool +QtLuaEngine::Protector::protect(const QVariant &var) +{ + QMutexLocker lock(&mutex); + int size = saved.size(); + saved.append(var); + if (! size) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + return true; +} + +bool +QtLuaEngine::Protector::event(QEvent *e) +{ + if (e->type() == QEvent::User) + { + QMutexLocker lock(&mutex); + saved.clear(); // possible actual deletion + return true; + } + return QObject::event(e); +} + + + +/* metatable material */ + +static int +luaQ_m__gc(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private_noerr(L); + QObject *obj = luaQ_toqobject(L, 1); + if (obj && d && d->isObjectLuaOwned(obj)) + obj->deleteLater(); + QVariant *vp = luaQ_toqvariantp(L, 1); + if (vp && d && d->protector) + d->protector->maybeProtect(*vp); + if (vp) + vp->QVariant::~QVariant(); + return 0; +} + + +static int +luaQ_m__type(lua_State *L) +{ + // LUA: "_val_:type()" + // Returns a string describing the type of a qt value. + // Returns nil if _val_ is not a qt value. + if (lua_isuserdata(L, 1) && + lua_getmetatable(L, 1)) + { + lua_pushliteral(L, "__typename"); + lua_rawget(L, -2); + if (lua_isstring(L, -1)) + return 1; + } + lua_pushstring(L, luaL_typename(L, 1)); + return 1; +} + + +static int +luaQ_m__isa(lua_State *L) +{ + // LUA: "_val_:isa(_str_)" + // Returns true is _val_ is an instance of class _str_. + // In the case of objects, the classname may or may not + // have a final star. + bool b = false; + QObject *obj; + const QVariant *vp = luaQ_toqvariantp(L, 1); + const char *t = luaL_checkstring(L, 2); + if (! vp) + b = (!strcmp(t, lua_typename(L, lua_type(L, 1)))); + else if (! qvariant_has_object_type(vp)) + b = (!strcmp(t, QMetaType::typeName(vp->userType())) || + !strcmp(t, vp->typeName()) ); + else if ((obj = luaQ_toqobject(L, 1))) + { + const QMetaObject *mo = obj->metaObject(); + int tlen = strlen(t); + if (tlen>0 && t[tlen-1]=='*') + tlen -= 1; + while (mo && !b) + { + const char *c = mo->className(); + b = (!strncmp(t, c, tlen) && c[tlen]==0); + mo = mo->superClass(); + } + } + lua_pushboolean(L, b); + return 1; +} + + +static int +luaQ_m__eq(lua_State *L) +{ + // __eq in metatable + const QVariant *vp1 = luaQ_toqvariantp(L, 1); + const QVariant *vp2 = luaQ_toqvariantp(L, 2); + bool ok = false; + if (*vp1 == *vp2) + ok = true; + else if (qvariant_has_object_type(vp1) && + qvariant_has_object_type(vp2) && + qvariant_to_object(vp1) == qvariant_to_object(vp2) ) + ok = true; + lua_pushboolean(L, ok); + return 1; +} + + +static int +luaQ_m__tostring(lua_State *L) +{ + // LUA: "_val_:tostring()" + // Converts a qt value to a nice printable string. + // Returns nil if _val_ is not a qt value. + const QVariant *vp = luaQ_toqvariantp(L, 1); + if (! vp) + lua_pushnil(L); + else if (qvariant_has_object_type(vp)) + { + QObject *obj = qvariant_to_object(vp); + if (obj) + lua_pushfstring(L, "qt.%s (%p)", obj->metaObject()->className(), obj); + else + lua_pushliteral(L, "qt.zombie"); + } + else + { + int type = vp->userType(); + const void *ptr = vp->constData(); + QVariant var = *vp; + if (var.type() == QVariant::String) + lua_pushstring(L, var.toString().toLocal8Bit().constData()); + else if (var.convert(QVariant::ByteArray)) + lua_pushstring(L, var.toByteArray().constData()); + else + lua_pushfstring(L, "qt.%s (%p)", QMetaType::typeName(type), ptr); + } + return 1; +} + + +static int +luaQ_m__tonumber(lua_State *L) +{ + // LUA: "_val_:tonumber()" + // Converts a qt value to a number. + // Returns nil if the conversion is impossible + QVariant var = luaQ_toqvariant(L, 1); + if (var.convert(QVariant::Double)) + lua_pushnumber(L, var.toDouble()); + else + lua_pushnil(L); + return 1; +} + + +static int +luaQ_m__tobool(lua_State *L) +{ + // LUA: "_val_:tobool()" + // Converts a qt value to a boolean. + const QVariant *vp = luaQ_toqvariantp(L, 1); + if (! vp) + lua_pushnil(L); + else if (qvariant_has_object_type(vp)) + lua_pushboolean(L, !!qvariant_to_object(vp)); + else + lua_pushboolean(L, !vp->isNull()); + return 1; +} + + +static int +luaQ_m__call(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushliteral(L, "new"); + lua_rawget(L, 1); + if (!lua_isfunction(L, -1)) + luaL_error(L, "constructor 'new' not found"); + lua_replace(L, 1); + lua_call(L, lua_gettop(L)-1, LUA_MULTRET); + return lua_gettop(L); +} + + +/* metatable material: get/set property */ + +static int +luaQ_m__getsetproperty(lua_State *L) +{ + const QVariant* vp = luaQ_toqvariantp(L, lua_upvalueindex(1)); + if (vp->userType() != qMetaTypeId<QtLuaPropertyInfo>()) + luaL_error(L, "internal error while accessing property"); + const QtLuaPropertyInfo *info = + static_cast<const QtLuaPropertyInfo*>(vp->constData()); + const QMetaProperty &mp = info->metaProperty; + QObject *obj = luaQ_toqobject(L, 1, info->metaObject); + if (! obj) + luaL_typerror(L, 0, info->metaObject->className()); + if (! mp.isScriptable(obj)) + luaL_error(L, "property " LUA_QS " is not scriptable", mp.name()); + if (lua_gettop(L) == 1) + { + if (! mp.isReadable()) + luaL_error(L, "property " LUA_QS " is not readable", mp.name()); + // get property + QVariant v = mp.read(obj); + // convert enum codes into enum names + QByteArray n; + if (mp.isFlagType() && v.canConvert(QVariant::Int)) + v = mp.enumerator().valueToKeys(v.toInt()); + else if (mp.isEnumType() && v.canConvert(QVariant::Int)) + v = mp.enumerator().valueToKey(v.toInt()); + // return + luaQ_pushqt(L, v); + return 1; + } + else + { + QVariant v = luaQ_toqvariant(L, 2); + if (! mp.isWritable()) + luaL_error(L, "property " LUA_QS " is not writable", mp.name()); + // string conversion + if (mp.type() == QVariant::String && v.type() == QVariant::ByteArray) + v = QString::fromLocal8Bit(v.toByteArray()); + // enum and flags conversion + if (mp.isFlagType() && v.type() == QVariant::ByteArray) + { + QMetaEnum me = mp.enumerator(); + int n = me.keysToValue(v.toByteArray().constData()); + if (n == -1) + luaL_error(L, "unrecognized flag for " LUA_QS, me.name()); + v = QVariant(n); + } + else if (mp.isEnumType() && v.type() == QVariant::ByteArray) + { + QMetaEnum me = mp.enumerator(); + int n = me.keyToValue(v.toByteArray().constData()); + if (n == -1) + luaL_error(L, "unrecognized enum for " LUA_QS, me.name()); + v = QVariant(n); + } + // check types + if (! mp.write(obj, v)) + luaL_error(L, "cannot convert value for property '%s'", mp.name()); + } + return 0; +} + + +/* metatable material: invoking methods */ + +static void * +make_argtype(QByteArray type) +{ + int tid = QMetaType::type(type); + if (type.endsWith("*")) + { + type.chop(1); + const QMetaObject *mo = qtLuaEngineGlobal()->findMetaObject(type); + if (mo) + return (void*)mo; + else if (!tid) + tid = QMetaType::VoidStar; + } + if (tid) + return (void*)((((size_t)tid)<<1)|1); + return 0; +} + + +static inline int +v_to_type(void *v) +{ + return (((size_t)v)&1) ? (((size_t)v)>>1) : 0; +} + + +static inline const QMetaObject * +v_to_metaobject(void *v) +{ + return (((size_t)v)&1) ? 0 : static_cast<const QMetaObject*>(v); +} + + +static const char * +v_to_name(void *v) +{ + int type = v_to_type(v); + if (type) + return QMetaType::typeName(type); + else if (v) + return v_to_metaobject(v)->className(); + else + return "void"; +} + + +static void * +construct_arg(QVariant &var, void *vtype) +{ + int type = v_to_type(vtype); + const QMetaObject *mo = v_to_metaobject(vtype); + QObject *obj; + if (type == QVariant::String && var.type() == QVariant::ByteArray) + var = QString::fromLocal8Bit(var.toByteArray()); + if (type == qMetaTypeId<QVariant>()) + return static_cast<void*>(new QVariant(var)); + else if (type && (var.userType() == type || var.convert(QVariant::Type(type)))) + return QMetaType::construct(type, var.constData()); + else if (mo && (obj = qvariant_to_object(&var))) + return static_cast<void*>(new QObject*(obj)); + return 0; +} + + +static void +destroy_arg(void *arg, void *vtype) +{ + int type = v_to_type(vtype); + const QMetaObject *mo = v_to_metaobject(vtype); + if (! arg) + return; + else if (type == qMetaTypeId<QVariant>()) + delete static_cast<QVariant*>(arg); + else if (type) + QMetaType::destroy(type, arg); + else if (mo) + delete static_cast<QObject**>(arg); +} + + +static void * +construct_retval(void *vtype) +{ + int type = v_to_type(vtype); + const QMetaObject *mo = v_to_metaobject(vtype); + if (mo) + return static_cast<void*>(new QObject*(0)); + else if (type == QMetaType::Void) + return 0; + else if (type == qMetaTypeId<QVariant>()) + return static_cast<void*>(new QVariant()); + else if (type) + return QMetaType::construct(type); + return 0; +} + + +static int +luaQ_p_push_retval(lua_State *L, void *arg, void *vtype) +{ + int type = v_to_type(vtype); + const QMetaObject *mo = v_to_metaobject(vtype); + if (! arg) + return 0; + if (type == qMetaTypeId<QVariant>()) + luaQ_pushqt(L, *static_cast<QVariant*>(arg)); + else if (type) + luaQ_pushqt(L, QVariant(type, arg)); + else if (mo) + luaQ_pushqt(L, *static_cast<QObject**>(arg)); + else + return 0; + return 1; +} + + +static inline int +type_class(int type) +{ + switch(type) + { + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::Char: + case QMetaType::Short: + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::LongLong: + case QMetaType::UChar: + case QMetaType::UShort: + case QMetaType::UInt: + case QMetaType::ULong: + case QMetaType::ULongLong: + return -1; // numerical + case QMetaType::QByteArray: + case QMetaType::QString: + return -2; // string + default: + return type; + } +} + + +static int +select_overload(VarVector &vars, const QtLuaMethodInfo *info) +{ + // fast path when single method + int overloads = info->d.size(); + if (overloads == 1) + return 0; + // fast path when single method with right number of args + int i; + int b = -1; + int bn = 0; + int narg = vars.size(); + for (i=0; i<overloads; i++) + if (narg == info->d[i].types.size()) + { b = i; bn += 1; } + if (b >= 0 && bn == 1) + return b; + // match types + b = -1; + bn = 0; + int bs = INT_MAX; + for (i=0; i<overloads; i++) + if (narg == info->d[i].types.size()) + { + int j; + int s = 0; + const QtLuaMethodInfo::Detail &d = info->d[i]; + for (j=1; j<narg; j++) + { + int type = v_to_type(d.types[j]); + int argtype = vars[j].userType(); + const QMetaObject *mo = v_to_metaobject(d.types[j]); + if (type == qMetaTypeId<QVariant>()) + continue; + if (type && type == argtype) + continue; + if (mo && qvariant_to_object(&vars[j], mo)) + continue; + s += 1; + if (type && type_class(type) == type_class(argtype)) + continue; + s += 10; + QVariant var = vars[j]; + if (var.canConvert(QVariant::Type(type))) + continue; + s += INT_MAX / 2; + break; + } + if (j < narg) + continue; + if (s == bs) + bn += 1; + else if (s < bs) + { b = i; bs = s; bn = 1; } + } + if (b >= 0 && bn == 1) + return b; + return -1; +} + + +static int +luaQ_m__invokemethod(lua_State *L) +{ + int i; + const int narg = lua_gettop(L) - 1; + const QVariant* vp = luaQ_toqvariantp(L, narg + 1); + if (vp->userType() != qMetaTypeId<QtLuaMethodInfo>()) + luaL_error(L, "internal error while invoking method"); + const QtLuaMethodInfo *info + = static_cast<const QtLuaMethodInfo*>(vp->constData()); + QObject *obj = luaQ_toqobject(L, 1, info->metaObject); + if (! obj) + luaL_error(L, "bad 'self' argument (%s expected)", + info->metaObject->className()); + VarVector vargs(narg); + for (i=1; i<narg; i++) + vargs[i] = luaQ_toqvariant(L, i+1); + int m = select_overload(vargs, info); + if (m < 0) + luaL_error(L, "cannot resolve ambiguous overload"); + const QtLuaMethodInfo::Detail &d = info->d[m]; + PtrVector pargs(narg); + int arg = 0; + int errarg = -1; + int nret = 0; + try + { + pargs[arg++] = construct_retval(d.types[0]); + for (int i=1; i<narg; i++) + if (! (pargs[arg++] = construct_arg(vargs[i], d.types[i]))) + { errarg = i; break ; } + // invoke + if (errarg < 0) + { + obj->qt_metacall(QMetaObject::InvokeMetaMethod, d.id, pargs.data()); + nret = luaQ_p_push_retval(L, pargs[0], d.types[0]); + } + // deallocate + while (--arg >= 0) + destroy_arg(pargs[arg], d.types[arg]); + // diagnosis + if (errarg >= 0) + luaL_error(L, "bad argument #%d (%s expected)", + errarg, v_to_name(d.types[errarg]) ); + } + catch(...) + { + while (--arg >= 0) + destroy_arg(pargs[arg], d.types[arg]); + throw; + } + return nret; +} + + +static int +luaQ_m__call_invokemethod(lua_State *L) +{ + QObject *obj = luaQ_toqobject(L, 1); + if (! obj) + luaL_error(L, "bad 'self' argument (not a qobject)"); + int narg = lua_gettop(L); + lua_pushcfunction(L, luaQ_m__invokemethod); + lua_insert(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + luaQ_call(L, narg + 1, LUA_MULTRET, obj); + return lua_gettop(L); +} + + +/* metatable material: index/newindex */ + +static int +luaQ_m__index(lua_State *L) +{ + // __index in metatable + // Check arguments + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + const QMetaObject *mo = obj->metaObject(); + if (lua_getmetatable(L, 1)) + { + lua_pushliteral(L, "__metatable"); + lua_rawget(L, -2); + // ..stack: object key metatable metaclass + // Search metaclass + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); + lua_gettable(L, -2); + // ..stack: object key metatable metaclass value + const QVariant *vp = luaQ_toqvariantp(L, -1); + int type = (vp) ? vp->userType() : QMetaType::Void; + if (type == qMetaTypeId<QtLuaMethodInfo>()) + { + lua_pushcclosure(L, luaQ_m__call_invokemethod, 1); + return 1; + } + else if (type == qMetaTypeId<QtLuaPropertyInfo>()) + { + lua_pushcclosure(L, luaQ_m__getsetproperty, 1); + lua_pushvalue(L, 1); + luaQ_call(L, 1, 1, obj); + return 1; + } + else if (! lua_isnil(L, -1)) + return 1; + lua_pop(L, 1); + } + lua_pop(L, 2); + } + // ..stack: object key + // Search children + const char *key = luaL_checkstring(L, 2); + QString name = QString::fromLocal8Bit(key); + foreach (QObject *o, obj->children()) + if (o->objectName() == name) + { + luaQ_pushqt(L, o); + return 1; + } + QObject *o = qFindChild<QObject*>(obj, QString::fromLocal8Bit(key)); + if (o) + { + luaQ_pushqt(L, o); + return 1; + } + // Failed + lua_pushnil(L); + return 1; +} + + +static int +luaQ_m__newindex(lua_State *L) +{ + // __newindex in metatables + // ..stack: object key value + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + const QMetaObject *mo = obj->metaObject(); + if (lua_getmetatable(L, 1)) + { + lua_pushliteral(L, "__metatable"); + lua_rawget(L, -2); + // ..stack: object key value metatable metaclass + // Search property + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); + lua_gettable(L, -2); + // ..stack: object key value metatable metaclass curval + const QVariant *vp = luaQ_toqvariantp(L, -1); + int type = (vp) ? vp->userType() : QMetaType::Void; + if (type == qMetaTypeId<QtLuaPropertyInfo>()) + { + lua_pushcclosure(L, luaQ_m__getsetproperty, 1); + lua_pushvalue(L, 1); + lua_pushvalue(L, 3); + luaQ_call(L, 2, 0, obj); + return 0; + } + lua_pop(L, 1); + } + lua_pop(L, 2); + } + // Failed + const char *key = luaL_checkstring(L, 2); + luaL_error(L, "cannot set unrecognized property '%s'", key); + return 0; +} + + +/* metaclasses */ + +static const luaL_Reg qtval_lib[] = { + { "tostring", luaQ_m__tostring }, + { "tonumber", luaQ_m__tonumber }, + { "tobool", luaQ_m__tobool }, + { "type", luaQ_m__type }, + { "isa", luaQ_m__isa }, + {NULL, NULL} +}; + + +void +luaQ_buildmetaclass(lua_State *L, int type) +{ + // Create table + lua_createtable(L, 0, 0); + // Fill table + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaQ_m__call); + lua_setfield(L, -2, "__call"); + if (type != QMetaType::Void) + { + luaQ_pushmeta(L, QMetaType::Void); + lua_pushliteral(L, "__metatable"); + lua_rawget(L, -2); + Q_ASSERT(lua_istable(L, -1)); + // stack: class metasuper classsuper + lua_setmetatable(L, -3); + lua_pop(L, 1); + } + else + { + // Standard methods + luaL_register(L, 0, qtval_lib); + } + // Insert class into qt package + lua_pushlightuserdata(L, (void*)qtKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); + lua_pushstring(L, QMetaType::typeName(type)); + lua_pushvalue(L, -3); + // ..stack: metaclass qtkeytable typename metaclass + lua_rawset(L, -3); + lua_pop(L, 1); +} + + +static PtrVector +make_argtypes(QMetaMethod method) +{ + PtrVector types; + QList<QByteArray> typeNames = method.parameterTypes(); + types += make_argtype(method.typeName()); + foreach(QByteArray b, typeNames) + types += make_argtype(b); + types.squeeze(); + return types; +} + + +void +luaQ_buildmetaclass(lua_State *L, const QMetaObject *mo) +{ + // Create table + lua_createtable(L, 0, 0); + // Fill table + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaQ_m__call); + lua_setfield(L, -2, "__call"); + const QMetaObject *super = mo->superClass(); + if (super) + { + // Plug superclass into index + luaQ_pushmeta(L, super); + lua_pushliteral(L, "__metatable"); + lua_rawget(L, -2); + Q_ASSERT(lua_istable(L, -1)); + // stack: class metasuper classsuper + lua_setmetatable(L, -3); + lua_pop(L, 1); + } + else + { + // Standard methods + luaL_register(L, 0, qtval_lib); + } + // Slots and invokable method + QtLuaEngine::Private *p = luaQ_private(L); + QMap<QByteArray,QtLuaMethodInfo> overloads; + int fm = mo->methodOffset(); + int lm = mo->methodCount(); + for (int i=fm; i<lm; i++) + { + QMetaMethod method = mo->method(i); + QByteArray sig = method.signature(); + if (method.access() != QMetaMethod::Private) + { + QtLuaMethodInfo::Detail d; + d.id = i; + d.types = make_argtypes(method); + QtLuaMethodInfo info; + info.metaObject = mo; + info.d += d; + info.d.squeeze(); + lua_pushstring(L, sig.constData()); + luaQ_pushqt(L, qVariantFromValue(info)); + // stack: class signature methodinfo + lua_rawset(L, -3); + // record overloads + int len = sig.indexOf('('); + if (len > 0) + { + sig = sig.left(len); + if (! overloads.contains(sig)) + overloads[sig] = info; + else { + QtLuaMethodInfo &oinfo = overloads[sig]; + oinfo.d += d; + } + } + } + } + // Overloaded methods + QMap<QByteArray,QtLuaMethodInfo>::const_iterator it; + for (it = overloads.constBegin(); it != overloads.constEnd(); ++it) + { + QtLuaMethodInfo info = it.value(); + info.d.squeeze(); + lua_pushstring(L, it.key().constData()); + luaQ_pushqt(L, qVariantFromValue(info)); + lua_rawset(L, -3); + } + // Properties + int fp = mo->propertyOffset(); + int lp = mo->propertyCount(); + for (int j=fp; j<lp; j++) + { + QtLuaPropertyInfo info; + info.id = j; + info.metaObject = mo; + info.metaProperty = mo->property(j); + lua_pushstring(L, info.metaProperty.name()); + luaQ_pushqt(L, qVariantFromValue(info)); + // stack: class name propinfo + lua_rawset(L, -3); + } + // Insert class into qt package + lua_pushlightuserdata(L, (void*)qtKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); + lua_pushstring(L, mo->className()); + lua_pushvalue(L, -3); + // ..stack: metaclass qtkeytable classname metaclass + lua_rawset(L, -3); + lua_pop(L, 1); +} + + +static const luaL_Reg qtmeta_lib[] = { + { "__tostring", luaQ_m__tostring }, + { "__eq", luaQ_m__eq }, + { "__gc", luaQ_m__gc }, + {NULL, NULL} +}; + + +void +luaQ_fillmetatable(lua_State *L, int type, const QMetaObject *mo) +{ + // Fill common entries + luaL_register(L, 0, qtmeta_lib); + // Fill distinct entries + if (mo) + { + luaQ_buildmetaclass(L, mo); + lua_setfield(L, -2, "__metatable"); + lua_pushcfunction(L, luaQ_m__index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaQ_m__newindex); + lua_setfield(L, -2, "__newindex"); + lua_pushfstring(L, "%s*", mo->className()); + lua_setfield(L, -2, "__typename"); + } + else + { + luaQ_buildmetaclass(L, type); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "__index"); + lua_setfield(L, -2, "__metatable"); + lua_pushstring(L, QMetaType::typeName(type)); + lua_setfield(L, -2, "__typename"); + } +} + + +/*! Pushes the metatable for qt values with type id \a type. */ + +void +luaQ_pushmeta(lua_State *L, int type) +{ + lua_pushlightuserdata(L, (void*)metaKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); + lua_pushlightuserdata(L, (void*)((((size_t)type)<<1)|1)); + lua_rawget(L, -2); + // stack: metakeytable metatable + if (lua_istable(L,-1)) + { + lua_remove(L, -2); + } + else + { + lua_createtable(L, 0, 6); + // stack: metakeytable nil metatable + lua_pushlightuserdata(L, (void*)((((size_t)type)<<1)|1)); + lua_pushvalue(L, -2); + lua_rawset(L, -5); + lua_pushvalue(L, -1); + lua_pushlightuserdata(L, (void*)qtKey); + lua_rawset(L, LUA_REGISTRYINDEX); + // fill metatable + luaQ_fillmetatable(L, type, 0); + lua_replace(L, -3); + lua_pop(L, 1); + } +} + + +/*! Pushes the metatable for qt objects with meta object \a mo. */ + +void +luaQ_pushmeta(lua_State *L, const QMetaObject *mo) +{ + lua_pushlightuserdata(L, (void*)metaKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); + lua_pushlightuserdata(L, (void*)mo); + lua_rawget(L, -2); + // ..stack: metakeytable metatable + if (lua_istable(L,-1)) + { + lua_remove(L, -2); + } + else + { + // record metaobject name + qtLuaEngineGlobal()->registerMetaObject(mo, false); + // create metatable + lua_createtable(L, 0, 6); + // stack: metakeytable maybehook metatable + lua_pushlightuserdata(L, (void*)mo); + lua_pushvalue(L, -2); + lua_rawset(L, -5); + lua_pushvalue(L, -1); + lua_pushlightuserdata(L, (void*)qtKey); + lua_rawset(L, LUA_REGISTRYINDEX); + // fill metatable + luaQ_fillmetatable(L, 0, mo); + lua_replace(L, -3); + lua_pop(L, 1); + } +} + + +/*! Pushes the metatable for qobject \a o. */ + +void +luaQ_pushmeta(lua_State *L, const QObject *o) +{ + luaQ_pushmeta(L, o->metaObject()); +} + + +/*! Pushes value \a var onto the stack. + Common variant types are converted + to standard lua types when applicable. */ + +void +luaQ_pushqt(lua_State *L, const QVariant &var) +{ + switch(var.userType()) + { + case QVariant::Invalid: + lua_pushnil(L); + break; + case QVariant::Bool: + lua_pushboolean(L, var.toBool()); + break; + case QVariant::Double: + case QVariant::Int: + case QVariant::ULongLong: + case QVariant::UInt: + case QMetaType::Float: + case QMetaType::Char: + case QMetaType::Short: + case QMetaType::Long: + case QMetaType::UChar: + case QMetaType::UShort: + case QMetaType::ULong: + lua_pushnumber(L, var.toDouble()); + break; + case QVariant::ByteArray: + { + QByteArray b = var.toByteArray(); + lua_pushlstring(L, b.constData(), b.size()); + break; + } + default: + if (qvariant_has_object_type(&var)) + { + luaQ_pushqt(L, qvariant_to_object(&var)); + } + else + { + void *v = lua_newuserdata(L, sizeof(QVariant)); + new (v) QVariant(var); + luaQ_pushmeta(L, var.userType()); + lua_setmetatable(L, -2); + break; + } + } +} + + +/*! Pushes qobject \obj onto the stack. + Flag \a owned indicates whether the object should + be deleted when lua deallocates the object descriptor. + Null object pointers are converted to lua \a nil value. */ + +void +luaQ_pushqt(lua_State *L, QObject *obj, bool owned) +{ + if (! obj) + { + lua_pushnil(L); + } + else + { + // Search already created objects + lua_pushlightuserdata(L, (void*)objectKey); + lua_rawget(L, LUA_REGISTRYINDEX); + Q_ASSERT(lua_istable(L, -1)); + lua_pushlightuserdata(L, (void*)obj); + lua_rawget(L, -2); + // ..stack: objecttable object + if (lua_isuserdata(L, -1)) + { + lua_remove(L, -2); + } + else + { + lua_pop(L, 1); + // ..stack: objecttable + // Make new userdata for object + QObjectPointer objp = obj; + QVariant objv = qVariantFromValue(objp); + void *v = lua_newuserdata(L, sizeof(QVariant)); + new (v) QVariant(objv); + // Fill object + luaQ_pushmeta(L, obj->metaObject()); + lua_setmetatable(L, -2); + // ..stack: objecttable object + // Record object + lua_pushlightuserdata(L, (void*)obj); + lua_pushvalue(L, -2); + // ..stack: objecttable object ud object + lua_rawset(L, -4); + // ..stack: objecttable object + lua_remove(L, -2); + // ..stack: object + if (owned) + { + QtLuaEngine::Private *d = luaQ_private(L); + if (d) + d->makeObjectLuaOwned(obj); + } + } + } +} + + + +// ======================================== +// Signal interception + + +struct QtLuaEngine::Receiver : public QObject +{ + Q_OBJECT +public: + virtual ~Receiver(); + Receiver(QtLuaEngine *q) : + QObject(q), d(q->d), sender(0), direct(false), args(0) {} +public slots: + void universal(); + void disconnect(); + bool connect(QObject *sender, const char *signal, bool direct); +public: + QPointer<QtLuaEngine::Private> d; + QPointer<QObject> sender; + QByteArray signature; + PtrVector types; + bool direct; +protected: + void **args; +}; + + +struct QtLuaEngine::Receiver2 +#ifndef Q_MOC_RUN + : public QtLuaEngine::Receiver +#endif +{ + Receiver2(QtLuaEngine *engine) : QtLuaEngine::Receiver(engine) {} + virtual int qt_metacall(QMetaObject::Call, int, void**); +}; + + +int +QtLuaEngine::Receiver2::qt_metacall(QMetaObject::Call c, int id, void **a) +{ + void **old = args; + args = a; + id = Receiver::qt_metacall(c,id,a); + args = old; + return id; +} + + +QtLuaEngine::Receiver::~Receiver() +{ + if (d && sender) + { + QtLuaQueuedSignal q; + q.delsignal = (void*)this; + QMutexLocker locker(&d->mutex); + d->queuedSignals += q; + } +} + + +void +QtLuaEngine::Receiver::disconnect() +{ + if (sender) + QObject::disconnect(sender, 0, this, 0); +} + + +bool +QtLuaEngine::Receiver::connect(QObject *obj, const char *sig, bool d) +{ + Q_ASSERT(sig); + Q_ASSERT(obj); + const QMetaObject *m = obj->metaObject(); + Q_ASSERT(m); + if (sender) + return false; + // search signal + if (sig[0]>= '0' && sig[0]<='3') + sig = sig + 1; + signature = sig; + int i = m->indexOfSignal(signature.constData()); + if (i < 0) + { + signature = QMetaObject::normalizedSignature(sig); + i = m->indexOfSignal(signature.constData()); + if (i < 0) + return false; + } + // metamethod + QMetaMethod method = m->method(i); + if (method.methodType() != QMetaMethod::Signal) + return false; + // setup + sender = obj; + types = make_argtypes(m->method(i)); + signature.prepend('0' + QSIGNAL_CODE); + if (QObject::connect(obj, signature.constData(), + this, SLOT(universal()), Qt::DirectConnection )) + { + QObject::connect(obj, SIGNAL(destroyed(QObject*)), + this, SLOT(deleteLater()) ); + direct = d; + return true; + } + else + { + sender = 0; + signature.clear(); + types.clear(); + return false; + } +} + + +void +QtLuaEngine::Receiver::universal() +{ + bool queueSignal = false; + QThread *mythread = QThread::currentThread(); + QMutexLocker locker(&d->mutex); + if (d && direct && !d->hopEvent && + d->lockCount>0 && d->lockThread == mythread) + { + // find closure + lua_State *L = d->L; + int base = lua_gettop(L); + lua_pushlightuserdata(L, (void*)signalKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (lua_istable(L, -1)) + { + lua_pushlightuserdata(L, (void*)this); + lua_rawget(L, -2); + lua_remove(L, -2); + if (lua_isfunction(L, -1)) + { + for (int i=1; i<types.size(); i++) + luaQ_p_push_retval(L, args[i], types[i]); + bool oldRflag = d->rflag; + d->rflag = true; + locker.unlock(); + if (! oldRflag) + emit d->q->stateChanged(QtLuaEngine::Running); + if (lua_pcall(L, types.size()-1, 0, 0)) + if (!d || d->printErrors) + luaQ_print(L, 1); + } + } + lua_settop(L, base); + } + else if (d) + { + // prepare queued signals + QtLuaQueuedSignal q; + q.sender = sender; + q.receiver = this; + q.delsignal = 0; + for (int i=1; i<types.size(); i++) + { + int type = v_to_type(types[i]); + const QMetaObject *mo = v_to_metaobject(types[i]); + if (type == qMetaTypeId<QVariant>()) + q.args += *static_cast<QVariant*>(args[i]); + else if (type) + q.args += QVariant(type, args[i]); + else if (mo) { + QObjectPointer p = *static_cast<QObject**>(args[i]); + q.args += qVariantFromValue(p); + } else + q.args += QVariant(); + } + // queue signals for later invocation + if (d->queuedSignals.isEmpty()) + queueSignal = true; + d->queuedSignals += q; + locker.unlock(); + } + if (queueSignal) + d->emitQueueSignal(); +} + + +void +QtLuaEngine::Private::disconnectAllSignals() +{ + Receiver *r; + foreach(QObject *obj, children()) + if ((obj) && (r = qobject_cast<Receiver*>(obj))) + r->disconnect(); + QMutexLocker locker(&mutex); + queuedSignals.clear(); +} + + +bool +QtLuaEngine::Private::processQueuedSignals(QMutexLocker &locker) +{ + bool processed = false; + QList<QtLuaQueuedSignal> qs = queuedSignals; + queuedSignals.clear(); + while (qs.size() > 0) + { + QtLuaQueuedSignal q = qs.takeFirst(); + if (q.delsignal) + { + // delete closure associated with this signal + lua_pushlightuserdata(L, (void*)signalKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (lua_istable(L, -1)) + { + lua_pushlightuserdata(L, (void*)this); + lua_pushnil(L); + lua_rawset(L, -3); + } + lua_pop(L, 1); + } + else if (q.sender && q.receiver && !pauseLoop && !unwindStack) + { + bool stateChanged = ! rflag; + rflag = true; + locker.unlock(); + if (stateChanged) + emitStateChanged(QtLuaEngine::Running); + // call closure + int base = lua_gettop(L); + lua_pushcfunction(L, lua_error_handler); + lua_pushlightuserdata(L, (void*)signalKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (lua_istable(L, -1)) + { + QtLuaEngine::Receiver *receiver = q.receiver; + lua_pushlightuserdata(L, (void*)receiver); + lua_rawget(L, -2); + lua_remove(L, -2); + if (lua_isfunction(L, -1)) + { + processed = true; + for (int i=0; i<q.args.size(); i++) + luaQ_pushqt(L, q.args[i]); + if (lua_pcall(L, q.args.size(), 0, -2-q.args.size())) + if (printErrors) + luaQ_print(L, 1); + } + } + lua_settop(L, base); + locker.relock(); + } + } + return processed; +} + + +/*! This function processes all pending events in the running thread. + It also processed queued signals connected to a lua function or closure. + This implies that these lua functions or closure are called from + within the luaQ_doevents function. + The optional flag \a wait indicates if one should wait + until receiving at least one event. */ + +void +luaQ_doevents(lua_State *L, bool wait) +{ + QtLuaEngine::Private *d = luaQ_private(L); + QMutexLocker locker(&d->mutex); + QEventLoop *saved_loop = d->pauseLoop; + bool saved_rflag = d->rflag; + // process events (state unchanged. should we pause on wait?) + QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents; + if (wait && d->queuedSignals.isEmpty()) + flags |= QEventLoop::WaitForMoreEvents; + locker.unlock(); + QCoreApplication::processEvents(flags); + locker.relock(); + // run signals (possibly going into Running state) + d->pauseLoop = 0; + if (saved_loop) + d->rflag = false; + while(d->processQueuedSignals(locker)) + { + locker.unlock(); + QCoreApplication::processEvents(); + locker.relock(); + } + // restore initial state + bool new_rflag = d->rflag; + d->rflag = saved_rflag; + d->pauseLoop = saved_loop; + locker.unlock(); + if (saved_loop && new_rflag) + emit d->emitStateChanged(QtLuaEngine::Paused); + else if (!saved_rflag && new_rflag) + emit d->emitStateChanged(QtLuaEngine::Ready); +} + + +void +luaQ_doevents(lua_State *L) +{ + return luaQ_doevents(L, false); +} + + +void +luaQ_pause(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private(L); + d->emitQueueSignal(); + lua_pushliteral(L, "pause"); + QMutexLocker locker(&d->mutex); + bool stateChanged = lua_pause_sub(L, d, locker); + locker.unlock(); + if (stateChanged) + d->emitStateChanged(QtLuaEngine::Running); + lua_pop(L, 1); + if (d && !d->unwindStack) + return; + lua_pushliteral(L, "stop"); + lua_error(L); +} + + +void +luaQ_resume(lua_State *L, bool nocontinue) +{ + QtLuaEngine::Private *d = luaQ_private(L); + QMutexLocker locker(&d->mutex); + if (d->pauseLoop) + return; + if (nocontinue) + d->unwindStack = true; + else + d->resumeFlag = true; +} + + + +/* create a receiver2 (receiver2, not receiver!) */ + +static int +luaQ_p_create_receiver(lua_State *L) +{ + QtLuaEngine::Private *d = luaQ_private(L); + QtLuaEngine::Receiver *r = new QtLuaEngine::Receiver2(d->q); + luaQ_pushqt(L, qVariantFromValue<void*>(static_cast<void*>(r))); + return 1; +} + + +/*! Connects signal \a sig of Qt object \a obj to the + function located at stack position \a findex. + Returns false if the signal does not exist. */ + +bool +luaQ_connect(lua_State *L, QObject *obj, + const char *sig, int findex, bool direct) +{ + bool success = false; + QtLuaEngine::Private *d = luaQ_private(L); + if (! lua_isfunction(L, findex)) + luaL_error(L, "luaQ_connect: function expected"); + // create receiver + lua_pushcfunction(L, luaQ_p_create_receiver); + luaQ_call(L, 0, 1, d->q); + QVariant *vp = luaQ_toqvariantp(L, -1); + void *v = (vp) ? qVariantValue<void*>(luaQ_toqvariant(L, -1)) : 0; + QtLuaEngine::Receiver *r = static_cast<QtLuaEngine::Receiver*>(v); + lua_pop(L, 1); + if (! r) + luaL_error(L, "luaQ_connect: cannot create receiver"); + // push closure + lua_pushvalue(L, findex); + lua_pushlightuserdata(L, (void*)signalKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (lua_istable(L, -1)) + { + lua_pushlightuserdata(L, (void*)r); + // ..stack: function signaltable receiveraddress + lua_pushvalue(L, -3); + lua_rawset(L, -3); + success = r->connect(obj, sig, direct); + if (! success) + r->deleteLater(); + } + lua_pop(L, 2); + return success; +} + + +/*! Disconnects the connection matching the provided arguments. + Arguments \a sig of \a findex can be zero to indicate + that any signature or any closure should match. + Returns a boolean indicating if any such signals were found. */ + +bool +luaQ_disconnect(lua_State *L, QObject *obj, const char *sig, int findex) +{ + QtLuaEngine::Private *d = luaQ_private(L); + if (sig && sig[0] == '0' + QSIGNAL_CODE) + sig = sig + 1; + else if (sig && sig[0] >= '0' && sig[0] <= '3') + return false; + if (findex && !lua_isfunction(L, findex)) + return false; + QByteArray nsig = QMetaObject::normalizedSignature(sig); + nsig.prepend('0' + QSIGNAL_CODE); + lua_pushlightuserdata(L, (void*)signalKey); + lua_rawget(L, LUA_REGISTRYINDEX); + if (findex) + lua_pushvalue(L, findex); + else + lua_pushnil(L); + // ..stack: function sigtable ... + // select receviers + QList<QtLuaEngine::Receiver*> chosen; + QMutexLocker locker(&d->mutex); + QtLuaEngine::Receiver *r; + foreach(QObject *o, d->q->children()) + if ((r = qobject_cast<QtLuaEngine::Receiver*>(o))) + { + if (obj && r->sender != obj) + continue; + if (sig && r->signature != nsig) + continue; + if (findex && lua_istable(L, -2)) + { + lua_pushlightuserdata(L, (void*)r); + lua_rawget(L, -3); + // ..stack: sigfunc function sigtable + bool eq = lua_equal(L, -1, -2); + lua_pop(L, 1); + if (! eq) + continue; + } + chosen += r; + } + // now disconnect and destroy the chosen receivers + locker.unlock(); + foreach(r, chosen) + { + r->disconnect(); + lua_pushlightuserdata(L, (void*)r); + lua_pushnil(L); + lua_rawset(L, -4); + r->deleteLater(); + } + // return + lua_pop(L, 2); + return chosen.size() > 0; +} + + + + +// ======================================== +// MOC + +#include "qtluaengine.moc" + + + + +/* ------------------------------------------------------------- + Local Variables: + c++-font-lock-extra-types: ("\\sw+_t" "\\(lua_\\)?[A-Z]\\sw*[a-z]\\sw*") + End: + ------------------------------------------------------------- */ + + diff --git a/qtlua/qtluaengine.h b/qtlua/qtluaengine.h new file mode 100644 index 0000000..a891d9c --- /dev/null +++ b/qtlua/qtluaengine.h @@ -0,0 +1,185 @@ +// -*- C++ -*- + +#ifndef QTLUAENGINE_H +#define QTLUAENGINE_H + +#include <QtGlobal> +#include <QByteArray> +#include <QList> +#include <QObject> +#include <QMetaObject> +#include <QMetaType> +#include <QPointer> +#include <QString> +#include <QStringList> +#include <QVariant> + +#include "lua.h" +#include "lauxlib.h" +#include "qtluaconf.h" + +typedef QPointer<QObject> QObjectPointer; + +Q_DECLARE_METATYPE(QObjectPointer) + +class QTLUAAPI QtLuaEngine : public QObject +{ + Q_OBJECT + Q_ENUMS(State); + Q_PROPERTY(QByteArray lastErrorMessage READ lastErrorMessage) + Q_PROPERTY(QStringList lastErrorLocation READ lastErrorLocation) + Q_PROPERTY(bool printResults READ printResults WRITE setPrintResults) + Q_PROPERTY(bool printErrors READ printErrors WRITE setPrintErrors) + Q_PROPERTY(bool pauseOnError READ pauseOnError WRITE setPauseOnError) + Q_PROPERTY(bool runSignalHandlers READ runSignalHandlers) + Q_PROPERTY(State state READ state); + Q_PROPERTY(bool ready READ isReady); + Q_PROPERTY(bool running READ isRunning); + Q_PROPERTY(bool paused READ isPaused); +public: + QtLuaEngine(QObject *parent = 0); + virtual ~QtLuaEngine(); + // meta objects + static void registerMetaObject(const QMetaObject *mo); + // named objects + void nameObject(QObject *obj, QString name=QString()); + QObject *namedObject(QString name); + QList<QObjectPointer> allNamedObjects(); + // state properties + enum State { Ready, Running, Paused }; + State state() const; + bool isReady() const { return state() == Ready; } + bool isRunning() const { return state() == Running; } + bool isPaused() const { return state() == Paused; } + bool isPausedOnError() const; + // other properties + QByteArray lastErrorMessage() const; + QStringList lastErrorLocation() const; + bool printResults() const; + bool printErrors() const; + bool pauseOnError() const; + bool runSignalHandlers() const; +signals: + void stateChanged(int state); + void errorMessage(QByteArray message); +public slots: + void setPrintResults(bool); + void setPrintErrors(bool); + void setPauseOnError(bool); + bool stop(bool nopause=false); + bool resume(bool nocontinue=false); + bool eval(QByteArray s, bool async=false); + bool eval(QString s, bool async=false); + QVariantList evaluate(QByteArray s); + QVariantList evaluate(QString s); +public: + struct Global; + struct Private; + struct Unlocker; + struct Catcher; + struct Protector; + struct Receiver; + struct Receiver2; +private: + Private *d; + lua_State *L; + friend class QtLuaLocker; +}; + + +class QTLUAAPI QtLuaLocker +{ +public: + explicit QtLuaLocker(QtLuaEngine *engine); + explicit QtLuaLocker(QtLuaEngine *engine, int timeOut); + ~QtLuaLocker(); + void setRunning(); + bool isReady() { return (count>0) && engine->isReady(); } + bool isPaused() { return (count>0) && engine->isPaused(); } + operator lua_State*() { return (count>0) ? engine->L : 0; } +private: + Q_DISABLE_COPY(QtLuaLocker) + QtLuaEngine *engine; + int count; +}; + + +QTLUAAPI QtLuaEngine *luaQ_engine(lua_State *L); +QTLUAAPI QVariant luaQ_toqvariant(lua_State *L, int i, int type = 0); +QTLUAAPI QObject* luaQ_toqobject(lua_State *L, int i, const QMetaObject *m=0); +QTLUAAPI void luaQ_pushqt(lua_State *L); +QTLUAAPI void luaQ_pushqt(lua_State *L, const QVariant &var); +QTLUAAPI void luaQ_pushqt(lua_State *L, QObject *obj, bool owned=false); +QTLUAAPI void luaQ_pushmeta(lua_State *L, int type); +QTLUAAPI void luaQ_pushmeta(lua_State *L, const QMetaObject *mo); +QTLUAAPI void luaQ_pushmeta(lua_State *L, const QObject *o); +QTLUAAPI bool luaQ_connect(lua_State *L, QObject *o, const char *s, int fi, + bool direct=false); +QTLUAAPI bool luaQ_disconnect(lua_State *L, QObject*o, const char *s, int fi); +QTLUAAPI void luaQ_doevents(lua_State *L, bool wait); +QTLUAAPI void luaQ_pause(lua_State *L); +QTLUAAPI void luaQ_resume(lua_State *L, bool nocontinue=false); +QTLUAAPI void luaQ_call(lua_State *L, int na, int nr, QObject *obj=0); +QTLUAAPI int luaQ_pcall(lua_State *L, int na, int nr, int eh, QObject *obj=0); +QTLUAAPI void luaQ_register(lua_State *L, const luaL_Reg *l, QObject *obj); + +template<typename T> inline bool +luaQ_isqobject(lua_State *L, int index, T* = 0) +{ + T *obj = qobject_cast<T*>(luaQ_toqobject(L, index)); + return (obj != 0); +} + +template<typename T> inline bool +luaQ_isqvariant(lua_State *L, int index, T* = 0) +{ + int type = qMetaTypeId<T>(); + QVariant v = luaQ_toqvariant(L, index, type); + return (v.userType() == type); +} + +template<typename T> inline T* +luaQ_checkqobject(lua_State *L, int index, T* = 0) +{ + T *obj = qobject_cast<T*>(luaQ_toqobject(L, index)); + if (! obj) + luaL_typerror(L, index, T::staticMetaObject.className()); + return obj; +} + +template<typename T> inline T +luaQ_checkqvariant(lua_State *L, int index, T* = 0) +{ + int type = qMetaTypeId<T>(); + QVariant v = luaQ_toqvariant(L, index, type); + if (v.userType() != type) + luaL_typerror(L, index, QMetaType::typeName(type)); + return qVariantValue<T>(v); +} + +template<typename T> inline T* +luaQ_optqobject(lua_State *L, int index, T *d = 0) +{ + if (!lua_isnoneornil(L, index)) + return luaQ_checkqobject<T>(L, index); + return d; +} + +template<typename T> inline T +luaQ_optqvariant(lua_State *L, int index, T d = T()) +{ + if (!lua_isnoneornil(L, index)) + return luaQ_checkqvariant<T>(L, index); + return d; +} + +#endif + + +/* ------------------------------------------------------------- + Local Variables: + c++-font-lock-extra-types: ("\\sw+_t" "\\(lua_\\)?[A-Z]\\sw*[a-z]\\sw*") + End: + ------------------------------------------------------------- */ + + diff --git a/qtlua/qtluautils.cpp b/qtlua/qtluautils.cpp new file mode 100644 index 0000000..4930f86 --- /dev/null +++ b/qtlua/qtluautils.cpp @@ -0,0 +1,712 @@ +// -*- C++ -*- + +#include "qtluautils.h" +#include "qtluaengine.h" + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include <QCoreApplication> +#include <QLibrary> +#include <QLine> +#include <QLineF> +#include <QMetaMethod> +#include <QMetaObject> +#include <QMetaType> +#include <QObject> +#include <QPoint> +#include <QPointF> +#include <QRect> +#include <QRectF> +#include <QStringList> +#include <QSize> +#include <QSizeF> +#include <QTimer> +#include <QUrl> +#include <QVariant> + + +// ======================================== +// More utilities + + + +static int +luaQ_gettable(struct lua_State *L) +{ + luaL_checktype(L, -2, LUA_TTABLE); + lua_gettable(L, -2); + return 1; +} + + +/*! Same as lua_getfield but returns \a nil + if an error occurs while processing the + metatable \a __index event. */ + +void +luaQ_getfield(struct lua_State *L, int index, const char *name) +{ + lua_pushvalue(L, index); + lua_pushcfunction(L, luaQ_gettable); + lua_insert(L, -2); + lua_pushstring(L, name); + lua_Hook hf = lua_gethook(L); + int hm = lua_gethookmask(L); + int hc = lua_gethookcount(L); + lua_sethook(L, 0, 0, 0); + if (lua_pcall(L, 2, 1, 0)) + { + lua_pop(L, 1); + lua_pushnil(L); + } + lua_sethook(L, hf, hm, hc); +} + + + + + +// ======================================== +// Error handlers + + + + +/*! Enrichs the message on the stack with a traceback. + This function must be called with one string argument + representing an error message for instance. + It safely augments the string with a stack trace + starting \a skip levels above the current level. + This function does not cause an error. + If something goes wrong during the stack exploration, + the function silently returns the unchanged error message. + */ + +int +luaQ_tracebackskip(struct lua_State *L, int skip) +{ + // stack: msg + luaQ_getfield(L, LUA_GLOBALSINDEX, "debug"); + luaQ_getfield(L, -1, "traceback"); + // stack: traceback debug msg + lua_remove(L, -2); + // stack: traceback msg + if (! lua_isfunction(L, -1)) + { + lua_pop(L, 1); + } + else + { + lua_pushvalue(L, -2); + lua_pushinteger(L, skip+1); + // save hook + lua_Hook hf = lua_gethook(L); + int hm = lua_gethookmask(L); + int hc = lua_gethookcount(L); + lua_sethook(L, 0, 0, 0); + // stack: skip msg traceback msg + if (lua_pcall(L, 2, 1, 0)) + // stack: err msg + lua_remove(L, -1); + else + // stack: msg msg + lua_remove(L, -2); + // restore hook + lua_sethook(L, hf, hm, hc); + } + // stack: msg + return 1; +} + + + +/*! A safe error handler that enrichs + the error message with a traceback. */ + +int +luaQ_traceback(struct lua_State *L) +{ + return luaQ_tracebackskip(L, 0); +} + + + + +// ======================================== +// Completion + + + +/*! Returns potential completion for a string. + Takes as input a single string composed of + identifiers separated by '.' or ':' and returns a table + representing an array of potential completions. + Each completion is a string that could be reasonably + appended to the initial argument. + This function throws errors when something goes wrong. + Use \a pcall to call it safely. */ + +int +luaQ_complete(struct lua_State *L) +{ + int k = 0; + int loop = 0; + const char *stem = luaL_checkstring(L, 1); + lua_pushvalue(L, LUA_GLOBALSINDEX); + for(;;) + { + const char *s = stem; + while (*s && *s != '.' && *s != ':') + s++; + if (*s == 0) + break; + // stack: table str + lua_pushlstring(L, stem, s-stem); + lua_gettable(L, -2); + // stack: ntable table str + lua_replace(L, -2); + // stack: ntable str + stem = s + 1; + } + lua_createtable(L, 0, 0); + lua_insert(L, -2); + // stack: maybetable anstable str + if (lua_isuserdata(L, -1) && lua_getmetatable(L, -1)) + { + lua_replace(L, -2); + lua_pushliteral(L, "__index"); + lua_rawget(L, -2); + if (lua_isfunction(L, -1)) + { + lua_pop(L, 1); + lua_pushliteral(L, "__metatable"); + lua_rawget(L, -2); + } + lua_replace(L, -2); + } + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return 1; + } + // stack: table anstable str + size_t stemlen = strlen(stem); + for(;;) + { + lua_pushnil(L); + while (lua_next(L, -2)) + { + // stack: value key table anstable str + bool ok = false; + size_t keylen; + const char *key = lua_tolstring(L, -2, &keylen); + if (key && keylen > 0 && keylen >= stemlen) + if (!strncmp(key, stem, stemlen)) + ok = true; + if (ok && !isalpha(key[0])) + ok = false; + if (ok) + for (int i=0; ok && i<(int)keylen; i++) + if (!isalpha(key[i]) && !isdigit(key[i]) && key[i]!='_') + ok = false; + if (ok) + { + const char *suffix = ""; + switch (lua_type(L, -1)) + { + case LUA_TFUNCTION: + suffix = "("; + break; + case LUA_TTABLE: + suffix = "."; + luaQ_getfield(L, -1, "_C"); + if (lua_istable(L, -1)) + suffix = ":"; + lua_pop(L, 1); + break; + case LUA_TUSERDATA: { + QVariant v = luaQ_toqvariant(L, -1); + const char *s = QMetaType::typeName(v.userType()); + if (s && !strcmp(s,"QtLuaMethodInfo")) + suffix = "("; + else if (s && !strcmp(s, "QtLuaPropertyInfo")) + suffix = ""; + else if (lua_getmetatable(L, -1)) { + lua_pop(L, 1); + suffix = ":"; + } else + suffix = ""; + } break; + default: + break; + } + // stack: value key table anstable str + lua_pushfstring(L, "%s%s", key+stemlen, suffix); + lua_rawseti(L, -5, ++k); + } + lua_pop(L, 1); + } + // stack: table anstable str + if (! lua_getmetatable(L, -1)) + break; + lua_replace(L, -2); + lua_pushliteral(L, "__index"); + lua_rawget(L, -2); + lua_replace(L, -2); + if (! lua_istable(L, -1)) + break; + if (++loop > 100) + luaL_error(L, "complete: infinite loop in metatables"); + } + // stack: something anstable str + lua_pop(L, 1); + lua_replace(L, -2); + return 1; +} + + + + +// ======================================== +// Printing + + + +static int +simple_print(lua_State *L) +{ + int i; + int nr = lua_gettop(L); + for (i=1; i<=nr; i++) + { + const char *s = lua_tostring(L, i); + printf("%s", s ? s : "???"); + printf("%c", i<nr ? '\t' : '\n'); + } + return 0; +} + + +int +luaQ_print(lua_State *L, int nr) +{ + int base = lua_gettop(L); + nr = qMin(lua_gettop(L), nr); + if (nr <= 0) + return 0; + lua_getglobal(L, "print"); + if (lua_type(L, -1) != LUA_TFUNCTION) + { + lua_pop(L, 1); + lua_pushcfunction(L, simple_print); + } + lua_checkstack(L, nr); + for (int i=base-nr+1; i<=base; i++) + lua_pushvalue(L, i); + if (lua_pcall(L, nr, 0, 0)) + { + const char *err = "error object is not a string"; + if (lua_isstring(L, -1)) + err = lua_tostring(L, -1); + printf("error calling 'print' (%s)\n", err); + lua_pop(L, 1); + } + return nr; +} + + + + +// ======================================== +// Cross-thread calls + + + +int +luaQ_pcall(lua_State *L, int na, int nr, int eh, int oh) +{ + QtLuaEngine *engine = luaQ_engine(L); + QObject *obj = engine; + if (oh) + obj = luaQ_toqobject(L, oh); + if (! obj) + luaL_error(L, "invalid qobject"); + return luaQ_pcall(L, na, nr, eh, obj); +} + + + + +// ======================================== +// Qt library functions + + + +static int +qt_connect(lua_State *L) +{ + // LUA: "qt.connect(object signal closure)" + // Connects signal to closure. + + // LUA: "qt.connect(object signal object signal_or_slot)" + // Connects signal to signal or slot. + + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + const char *sig = luaL_checkstring(L, 2); + QObject *robj = luaQ_toqobject(L, 3); + if (robj) + { + // search signal or slot + QByteArray rsig = luaL_checkstring(L, 4); + const QMetaObject *mo = robj->metaObject(); + int idx = mo->indexOfMethod(rsig.constData()); + if (idx < 0) + { + rsig = QMetaObject::normalizedSignature(rsig.constData()); + idx = mo->indexOfMethod(rsig.constData()); + if (idx < 0) + luaL_error(L, "cannot find target slot or signal %s", + rsig.constData()); + } + // prepend signal or slot indicator + QMetaMethod method = mo->method(idx); + if (method.methodType() == QMetaMethod::Signal) + rsig.prepend('0' + QSIGNAL_CODE); + else if (method.methodType() == QMetaMethod::Slot) + rsig.prepend('0' + QSLOT_CODE); + else + luaL_error(L, "target %s is not a slot or a signal", + rsig.constData()); + // connect + QByteArray ssig = sig; + ssig.prepend('0' + QSIGNAL_CODE); + if (! QObject::connect(obj, ssig.constData(), robj, rsig.constData())) + luaL_error(L, "cannot find source signal %s", sig); + } + else + { + luaL_checktype(L, 3, LUA_TFUNCTION); + bool direct = lua_toboolean(L, 4); + if (direct) + luaL_checktype(L, 4, LUA_TBOOLEAN); + if (! luaQ_connect(L, obj, sig, 3, direct)) + luaL_error(L, "cannot find source signal %s", sig); + } + return 0; +} + + +static int +qt_disconnect(lua_State *L) +{ + // LUA: qt.disconnect(object [signal [closure]]) + // LUA: qt.disconnect(object signal object signal_or_slot) + // Disconnect all connections between + // the specified signal and a lua function. + // Returns boolean indicating if such signal were found. + bool ok = false; + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + const char *sig = luaL_optstring(L, 2, 0); + int narg = lua_gettop(L); + if (narg>3 || lua_isuserdata(L, 3)) + { + QObject *robj = luaQ_optqobject<QObject>(L, 3, 0); + const char *rsig = luaL_optstring(L, 4, 0); + QByteArray bsig(sig); + QByteArray brsig(rsig); + bsig.prepend('0'+QSIGNAL_CODE); + brsig.prepend('0'+QSLOT_CODE); + sig = (sig) ? bsig.constData() : 0; + rsig = (rsig) ? brsig.constData() : 0; + ok = QObject::disconnect(obj, sig, robj, rsig); + brsig[0] = '0' + QSIGNAL_CODE; + if (rsig) + ok |= QObject::disconnect(obj, sig, robj, brsig.constData()); + } + else + { + int findex = 3; + if (lua_isnoneornil(L, 3)) + findex = 0; + else if (! lua_isfunction(L, 3)) + luaL_typerror(L, 3, "function"); + ok = luaQ_disconnect(L, obj, sig, findex); + } + lua_pushboolean(L, ok); + return 1; +} + + +static int +qt_doevents(lua_State *L) +{ + luaQ_doevents(L, lua_toboolean(L, 1)); + return 0; +} + + +static int +qt_qcall(lua_State *L) +{ + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + int base = 2; + int narg = lua_gettop(L) - base; + int status = luaQ_pcall(L, narg, LUA_MULTRET, 0, obj); + lua_pushboolean(L, !status); + lua_insert(L, base); + return lua_gettop(L) - base + 1; +} + + +static int +qt_xqcall(lua_State *L) +{ + QObject *obj = luaQ_checkqobject<QObject>(L, 1); + lua_insert(L, 2); + luaL_checktype(L, 2, LUA_TFUNCTION); + luaL_checktype(L, 3, LUA_TFUNCTION); + int base = 3; + int narg = lua_gettop(L) - base; + int status = luaQ_pcall(L, narg, LUA_MULTRET, 2, obj); + lua_pushboolean(L, !status); + lua_insert(L, base); + return lua_gettop(L) - base + 1; +} + + +static int +qt_type(lua_State *L) +{ + QVariant v; + if (lua_type(L, 1) == LUA_TUSERDATA) + v = luaQ_toqvariant(L, 1); + if (v.type() != QVariant::Invalid) + { + lua_pushvalue(L, 1); + lua_getfield(L, -1, "type"); + if (lua_isfunction(L, -1)) + { + lua_insert(L, -2); + lua_call(L, 1, 1); + return 1; + } + } + lua_pushnil(L); + return 1; +} + + +static int +qt_isa(lua_State *L) +{ + QVariant v; + if (lua_type(L, 1) == LUA_TUSERDATA) + v = luaQ_toqvariant(L, 1); + if (v.type() != QVariant::Invalid) + { + lua_pushvalue(L, 1); + lua_getfield(L, -1, "isa"); + if (lua_isfunction(L, -1)) + { + lua_insert(L, 1); + lua_call(L, lua_gettop(L)-1, 1); + return 1; + } + } + lua_pushnil(L); + return 1; +} + + + +// {{{ functions copied or derived from loadlib.c + +static int readable (const char *filename) +{ + FILE *f = fopen(filename, "r"); /* try to open file */ + if (f == NULL) return 0; /* open failed */ + fclose(f); + return 1; +} + +static const char *pushnexttemplate (lua_State *L, const char *path) +{ + const char *l; + while (*path == *LUA_PATHSEP) path++; /* skip separators */ + if (*path == '\0') return NULL; /* no more templates */ + l = strchr(path, *LUA_PATHSEP); /* find next separator */ + if (l == NULL) l = path + strlen(path); + lua_pushlstring(L, path, l - path); /* template */ + return l; +} + +static const char *pushfilename (lua_State *L, const char *name) +{ + const char *path; + const char *filename; + luaQ_getfield(L, LUA_GLOBALSINDEX, "package"); + luaQ_getfield(L, -1, "cpath"); + lua_remove(L, -2); + if (! (path = lua_tostring(L, -1))) + luaL_error(L, LUA_QL("package.cpath") " must be a string"); + lua_pushliteral(L, ""); + while ((path = pushnexttemplate(L, path))) { + filename = luaL_gsub(L, lua_tostring(L, -1), "?", name); + lua_remove(L, -2); + if (readable(filename)) + { // stack: cpath errmsg filename + lua_remove(L, -3); + lua_remove(L, -2); + return lua_tostring(L, -1); + } + lua_pushfstring(L, "\n\tno file " LUA_QS, filename); + lua_remove(L, -2); /* remove file name */ + lua_concat(L, 2); /* add entry to possible error message */ + } + lua_pushfstring(L, "module " LUA_QS " not found", name); + lua_replace(L, -3); + lua_concat(L, 2); + lua_error(L); + return 0; +} + +// functions copied or derived from loadlib.c }}} + +static int +qt_require(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + lua_settop(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); // index 2 + lua_getfield(L, 2, name); + if (lua_toboolean(L, -1)) + return 1; + const char *filename = pushfilename(L, name); // index 3 + QLibrary library(QString::fromLocal8Bit(filename)); + library.setLoadHints(QLibrary::ExportExternalSymbolsHint); + if (! library.load()) + luaL_error(L, "cannot load " LUA_QS, filename); + lua_pushfstring(L, "luaopen_%s", name); // index 4 + lua_CFunction func = (lua_CFunction)library.resolve(lua_tostring(L, -1)); + if (! func) + luaL_error(L, "no symbol " LUA_QS " in module " LUA_QS, + lua_tostring(L, -1), filename); + lua_pushboolean(L, 1); + lua_setfield(L, 2, name); + lua_pushcfunction(L, func); + lua_pushstring(L, name); + lua_call(L, 1, 1); + if (! lua_isnil(L, -1)) + lua_setfield(L, 2, name); + lua_getfield(L, 2, name); + return 1; +} + + +static int +qt_pause(lua_State *L) +{ + luaQ_pause(L); + return 0; +} + +static int +qt_resume(lua_State *L) +{ + luaQ_resume(L, lua_toboolean(L, 1)); + return 0; +} + + +static const luaL_Reg qt_lib[] = { + {"connect", qt_connect}, + {"disconnect", qt_disconnect}, + {"doevents", qt_doevents}, + {"qcall", qt_qcall}, + {"xqcall", qt_xqcall}, + {"type", qt_type}, + {"typename", qt_type}, + {"isa", qt_isa}, + {"require", qt_require}, + {"pause", qt_pause}, + {"resume", qt_resume}, + {0, 0} +}; + + +static int +qt_m__index(lua_State *L) +{ + const char *s = luaL_checkstring(L, 2); + QtLuaEngine *engine = luaQ_engine(L); + QObject *obj = engine->namedObject(s); + if (obj) + luaQ_pushqt(L, obj); + else + lua_pushnil(L); + return 1; +} + + +// ======================================== +// Initialize + +static int +no_methodcall(lua_State *L) +{ + luaL_error(L, "This class prevents lua to call this method"); + return 0; +} + +static void +hide_deletelater(lua_State *L, const QMetaObject *mo) +{ + luaQ_pushmeta(L, mo); + lua_getfield(L, -1, "__metatable"); + // ..stack: metaclass + lua_pushcfunction(L, no_methodcall); + lua_setfield(L, -2, "deleteLater"); + lua_pushcfunction(L, no_methodcall); + lua_setfield(L, -2, "deleteLater(QObject*)"); + // restore + lua_pop(L, 1); +} + + +int +luaopen_qt(lua_State *L) +{ + const char *qt = luaL_optstring(L, 1, "qt"); + luaQ_pushqt(L); + lua_pushvalue(L, -1); + lua_setfield(L, LUA_GLOBALSINDEX, qt); + luaL_register(L, qt, qt_lib); + + // Add qt_m_index in a metatable + lua_createtable(L, 0, 1); + lua_pushcfunction(L, qt_m__index); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + + // Hide deleteLater in selected classes + hide_deletelater(L, &QCoreApplication::staticMetaObject); + hide_deletelater(L, &QtLuaEngine::staticMetaObject); + + return 1; +} + + + + + +/* ------------------------------------------------------------- + Local Variables: + c++-font-lock-extra-types: ("\\sw+_t" "\\(lua_\\)?[A-Z]\\sw*[a-z]\\sw*") + End: + ------------------------------------------------------------- */ + + diff --git a/qtlua/qtluautils.h b/qtlua/qtluautils.h new file mode 100644 index 0000000..634efee --- /dev/null +++ b/qtlua/qtluautils.h @@ -0,0 +1,46 @@ +// -*- C -*- + +#ifndef QTLUAUTILS_H +#define QTLUAUTILS_H + +#include "lua.h" +#include "lauxlib.h" +#include "qtluaconf.h" + +#ifdef WIN32 +# ifdef libqtlua_EXPORTS +# define QTLUAAPI __declspec(dllexport) +# else +# define QTLUAAPI __declspec(dllimport) +# endif +#else +# define QTLUAAPI +#endif + + +#ifdef __cplusplus +# define QTLUA_EXTERNC extern "C" +#else +# define QTLUA_EXTERNC extern +#endif + +QTLUA_EXTERNC QTLUAAPI void luaQ_getfield(lua_State *L, int index, const char *name); +QTLUA_EXTERNC QTLUAAPI int luaQ_tracebackskip(lua_State *L, int skip); +QTLUA_EXTERNC QTLUAAPI int luaQ_traceback(lua_State *L); +QTLUA_EXTERNC QTLUAAPI int luaQ_complete(lua_State *L); +QTLUA_EXTERNC QTLUAAPI int luaQ_print(lua_State *L, int nr); +QTLUA_EXTERNC QTLUAAPI int luaQ_pcall(lua_State *L, int na, int nr, int eh, int oh); +QTLUA_EXTERNC QTLUAAPI void luaQ_doevents(lua_State *L); +QTLUA_EXTERNC QTLUAAPI int luaopen_qt(lua_State *L); + +#endif + + + + +/* ------------------------------------------------------------- + Local Variables: + c++-font-lock-extra-types: ( "\\sw+_t" "lua_[A-Z]\\sw*[a-z]\\sw*" ) + c-font-lock-extra-types: ( "\\sw+_t" "lua_[A-Z]\\sw*[a-z]\\sw*" ) + End: + ------------------------------------------------------------- */ |