summaryrefslogtreecommitdiffstats
path: root/library/scriptadapter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'library/scriptadapter.cpp')
-rw-r--r--library/scriptadapter.cpp214
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();
+}