diff options
Diffstat (limited to 'library/scriptadapter.cpp')
-rw-r--r-- | library/scriptadapter.cpp | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/library/scriptadapter.cpp b/library/scriptadapter.cpp new file mode 100644 index 0000000..dabf103 --- /dev/null +++ b/library/scriptadapter.cpp @@ -0,0 +1,214 @@ +#include "scriptadapter.h" + +#include "optionsitem.h" + +#include <QtCore/QMetaEnum> +#include <QtCore/QCoreApplication> +#include <QtCore/QTimer> +#include <QtCore/QTime> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtGui/QMessageBox> + +template <typename Enum> +static void scriptToEnum(const QScriptValue &obj, Enum &e) +{ + e = static_cast<Enum>(obj.toInt32()); +} + +template <typename Enum> +static QScriptValue enumToScript(QScriptEngine *engine, const Enum &e) +{ + QScriptValue obj = engine->newVariant(e); + return obj; +} + +// Adds a property 'name' to 'to' that contains all the values of the enum 'name', +// which must be registered in 'mo'. Also registers the type 'Enum' to the QScriptEngine. +template <typename Enum> +static void exposeEnum(QScriptEngine *engine, QScriptValue *to, const char *name, const QMetaObject *mo) +{ + qScriptRegisterMetaType<Enum>(engine, &enumToScript<Enum>, &scriptToEnum<Enum>); + QScriptValue enumvals = engine->newObject(); + int enumIndex = mo->indexOfEnumerator(name); + Q_ASSERT(enumIndex != -1 && "enum not found in meta object"); + QMetaEnum metaEnum = mo->enumerator(enumIndex); + for (int i = 0; i < metaEnum.keyCount(); ++i) + enumvals.setProperty(metaEnum.key(i), metaEnum.value(i)); + to->setProperty(name, enumvals); +} + +ScriptAdapter::ScriptAdapter(QObject *parent) + : QObject(parent) +{ + mScriptRunner = new QTimer(this); + mScriptRunner->setSingleShot(true); + mScriptRunner->setInterval(100); + connect(mScriptRunner, SIGNAL(timeout()), this, SLOT(continueScripts())); + mScriptRunner->start(); + + mMessageBox = new QMessageBox; +} + +ScriptAdapter::~ScriptAdapter() +{ + delete mMessageBox; +} + +void ScriptAdapter::addScriptInterface(const QString &name, QObject *interface) +{ + mScriptInterfaces.insert(name, interface); +} + +ScriptFiber *ScriptAdapter::script(int index) +{ + if (index >= 0 && index < mActiveScripts.length()) + return mActiveScripts[index]; + else + return 0; +} + +void ScriptAdapter::runAutostartScripts() +{ + QDir dir(QCoreApplication::applicationDirPath()); + dir.cd(QLatin1String("scripts")); + dir.cd(QLatin1String("autostart")); + + QStringList scriptPatterns; + scriptPatterns << "*.js" << "*.qs"; + foreach (const QFileInfo &file, dir.entryInfoList(scriptPatterns, QDir::Files)) { + run(file.filePath()); + } +} + +ScriptFiber *ScriptAdapter::run(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Could not read script file: " << filePath; + return 0; + } + + QFileInfo fileInfo(file); + ScriptFiber *script = new ScriptFiber(fileInfo.baseName(), file.readAll(), this); + mActiveScripts += script; + + emit scriptStart(script); + + return script; +} + +void ScriptAdapter::continueScripts() { + for (int i = 0; i < mActiveScripts.length(); ++i) { + ScriptFiber *script = mActiveScripts[i]; + + if (script->status() == Fiber::Terminated) { + emit scriptStop(i); + + mActiveScripts.removeOne(script); + delete script; + break; + } + + if (!script->isPaused() || script->isTerminating()) { + script->cont(); + } + } + + mScriptRunner->start(); +} + +void ScriptAdapter::yield(int ms) +{ + ScriptFiber *scriptFiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber()); + if (!scriptFiber) + return; + + QTime timer; + timer.start(); + while (timer.elapsed() < ms && !scriptFiber->isTerminating()) { + scriptFiber->suspend(); + } +} + +void ScriptAdapter::messageBox(const QString &text) +{ + mMessageBox->setText(text); + + // message boxes should block + ScriptFiber *fiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber()); + fiber->beginBlocking(); + mMessageBox->exec(); + fiber->endBlocking(); +} + +ScriptFiber::ScriptFiber(const QString &name, const QString &scriptCode, + ScriptAdapter *adapter) + : Fiber(524228) // FIXME: 131072 was not big enough... + , mName(name) + , mScriptCode(scriptCode) + , mAdapter(adapter) + , mPaused(false) + , mTerminate(false) +{ + mStopTimer.setInterval(10); + connect(&mStopTimer, SIGNAL(timeout()), this, SLOT(suspend())); + + // trigger event processing during script evaluation + mEngine.setProcessEventsInterval(10); + + // inject the ScriptAdapters functions into the global scope + QScriptValue adapterValue = mEngine.newQObject(adapter); + adapterValue.setPrototype(mEngine.globalObject()); + mEngine.setGlobalObject(adapterValue); + + QHashIterator<QString, QObject *> iter(mAdapter->mScriptInterfaces); + while (iter.hasNext()) { + iter.next(); + QScriptValue interface = mEngine.newQObject(iter.value()); + mEngine.globalObject().setProperty(iter.key(), interface); + } +} + +void ScriptFiber::run() +{ + mStopTimer.start(); + QScriptValue value = mEngine.evaluate(mScriptCode); + if (value.isError()) { + qWarning() << "Script execution resulted in an error:" << value.toString(); + } + mStopTimer.stop(); +} + +void ScriptFiber::requestTerminate() +{ + mTerminate = true; +} + +void ScriptFiber::togglePause() +{ + mPaused = !mPaused; +} + +void ScriptFiber::suspend() +{ + if (Fiber::currentFiber() == this) { + Fiber::yield(); + + if (mTerminate) { + mEngine.abortEvaluation(mEngine.currentContext()->throwError("Aborted")); + mStopTimer.stop(); + } + } +} + +void ScriptFiber::beginBlocking() +{ + mStopTimer.stop(); +} + +void ScriptFiber::endBlocking() +{ + mStopTimer.start(); +} |