summaryrefslogtreecommitdiffstats
path: root/src/qscxml.cpp
diff options
context:
space:
mode:
authorNoam Rosenthal <nrosenth@nokia.com>2009-06-08 12:27:03 -0700
committerNoam Rosenthal <nrosenth@nokia.com>2009-06-08 12:27:03 -0700
commitd0441f605434a89b53735427e4e81182c65debbd (patch)
treeb96d25dc89cdb523c007a22bc0deed3a5aa5dd56 /src/qscxml.cpp
parenta6553f68f17c28adca049857686496a69b4c1e7a (diff)
scxml for 4.6
Diffstat (limited to 'src/qscxml.cpp')
-rw-r--r--src/qscxml.cpp1428
1 files changed, 1428 insertions, 0 deletions
diff --git a/src/qscxml.cpp b/src/qscxml.cpp
new file mode 100644
index 0000000..8190056
--- /dev/null
+++ b/src/qscxml.cpp
@@ -0,0 +1,1428 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+/*!
+ \class QScxml
+ \reentrant
+
+ \brief The QScxml class provides a way to use scripting with the Qt State Machine Framework.
+
+ \ingroup sctools
+
+ Though can be used alone, QScxml is mainly a runtime helper to using the
+ state-machine framework with SCXML files.
+
+
+ \sa QStateMachine
+*/
+
+#include "qscxml.h"
+#include <QScriptEngine>
+#include <QScriptValueIterator>
+#include <QDebug>
+#include <QTimer>
+#include <QSignalMapper>
+#include <QUuid>
+#include <QHash>
+#include <QXmlStreamReader>
+#include <QFileInfo>
+#include <QDir>
+#include <QSet>
+#include <QStack>
+#include <QHistoryState>
+#include <QFinalState>
+#include <QState>
+#ifdef QT_GUI_LIB
+#include "qscxmlgui.h"
+#endif
+
+
+class QScxmlPrivate
+{
+ public:
+
+ enum { MaxSnapshots = 200};
+
+ struct AnchorSnapshot
+ {
+ QAbstractState* state;
+ QString location;
+ QScriptValue snapshot;
+ QString anchorType;
+ };
+
+
+ QScriptEngine* scriptEng;
+ QList<QScxmlInvokerFactory*> invokerFactories;
+ QUrl burl;
+ QString sessionID;
+ QString startScript;
+
+
+ QStack<AnchorSnapshot> snapshotStack;
+ QMultiHash<QString,QAbstractTransition*> anchorTransitions;
+ QHash<QString,AnchorSnapshot> curSnapshot;
+
+
+ static QHash<QString,QScxml*> sessions;
+};
+QHash<QString,QScxml*> QScxmlPrivate::sessions;
+
+class QScxmlTimer : public QObject
+{
+ Q_OBJECT
+ public:
+ QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine),script(scr)
+ {
+ startTimer(delay);
+ }
+ protected:
+ void timerEvent(QTimerEvent*)
+ {
+ if (script.isFunction())
+ script.call();
+ else if (script.isString())
+ script.engine()->evaluate(script.toString());
+ }
+
+ private:
+ QScriptValue script;
+
+};
+
+static QScriptValue _q_deepCopy(const QScriptValue & val)
+{
+ if (val.isObject() || val.isArray()) {
+ QScriptValue v = val.isArray() ? val.engine()->newArray() : val.engine()->newObject();
+ v.setData(val.data());
+ QScriptValueIterator it (val);
+ while (it.hasNext()) {
+ it.next();
+ v.setProperty(it.name(), _q_deepCopy(it.value()));
+ }
+ return v;
+ } else
+ return val;
+}
+
+
+struct QScxmlFunctions
+{
+static QScriptValue cssTime(QScriptContext *context, QScriptEngine *engine)
+{
+ QString str;
+ if (context->argumentCount() > 0)
+ str = context->argument(0).toString();
+ if (str == "") {
+ return qScriptValueFromValue<int>(engine,0);
+ }
+ else if (str.endsWith("ms")) {
+ return qScriptValueFromValue<int>(engine,(str.left(str.length()-2).toInt()));
+ }
+ else if (str.endsWith("s")) {
+ return qScriptValueFromValue<int>(engine,(str.left(str.length()-1).toInt())*1000);
+ }
+ else {
+ return qScriptValueFromValue<int>(engine, (str.toInt()));
+ }
+}
+static QScriptValue setTimeout(QScriptContext *context, QScriptEngine *engine)
+{
+ if (context->argumentCount() < 2)
+ return QScriptValue();
+ int timeout = context->argument(1).toInt32();
+ QScxmlTimer* tmr = new QScxmlTimer(engine,context->argument(0),timeout);
+ return engine->newQObject(tmr);
+}
+static QScriptValue script_print(QScriptContext *context, QScriptEngine *)
+{
+ if (context->argumentCount() > 0)
+ qDebug() << context->argument(0).toString();
+ return QScriptValue();
+}
+static QScriptValue clearTimeout(QScriptContext *context, QScriptEngine *)
+{
+ if (context->argumentCount() > 0) {
+ QObject* obj = context->argument(0).toQObject();
+ obj->deleteLater();
+ }
+ return QScriptValue();
+}
+
+static QScriptValue deepCopy(QScriptContext *context, QScriptEngine *)
+{
+ if (context->argumentCount() == 0)
+ return QScriptValue();
+ else
+ return _q_deepCopy(context->argument(0));
+}
+
+static QScriptValue receiveSignal(QScriptContext *context, QScriptEngine *engine)
+{
+ QString eventName = context->thisObject().property("e").toString();
+ if (!eventName.isEmpty()) {
+ QScxml* scxml = qobject_cast<QScxml*>(engine->globalObject().property("scxml.stateMachine").toQObject());
+ if (scxml) {
+ QStringList pnames;
+ QVariantList pvals;
+ for (int i=0; i < context->argumentCount(); ++i) {
+ pnames << QString::number(i);
+ pvals << context->argument(i).toVariant();
+ }
+ QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,QScriptValue());
+ ev->metaData.kind = QScxmlEvent::MetaData::Platform;
+ scxml->postEvent(ev);
+ }
+ }
+ return QScriptValue();
+}
+
+static QScriptValue postEvent(QScriptContext *context, QScriptEngine *engine)
+{
+ QScxml* scxml = qobject_cast<QScxml*>(engine->globalObject().property("scxml").toQObject());
+ if (scxml) {
+ QString eventName,target,type;
+ QStringList pnames;
+ QVariantList pvals;
+ QScriptValue cnt;
+ if (context->argumentCount() > 0)
+ eventName = context->argument(0).toString();
+ if (context->argumentCount() > 1)
+ target = context->argument(1).toString();
+ if (context->argumentCount() > 2)
+ type = context->argument(2).toString();
+
+ if (!eventName.isEmpty() || !target.isEmpty()) {
+ if (context->argumentCount() > 3)
+ qScriptValueToSequence<QStringList>(context->argument(3),pnames);
+ if (context->argumentCount() > 4) {
+ QScriptValueIterator it (context->argument(4));
+ while (it.hasNext()) {
+ it.next();
+ pvals.append(it.value().toVariant());
+ }
+ } if (context->argumentCount() > 5)
+ cnt = context->argument(5);
+ QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,cnt);
+ if (type == "scxml" || type == "") {
+ bool ok = true;
+ if (target == "_internal") {
+ ev->metaData.kind = QScxmlEvent::MetaData::Internal;
+ scxml->postInternalEvent(ev);
+ } else if (target == "scxml" || target == "") {
+ ev->metaData.kind = QScxmlEvent::MetaData::External;
+ scxml->postEvent(ev);
+ } else if (target == "_parent") {
+ QScxmlInvoker* p = qobject_cast<QScxmlInvoker*>(scxml->parent());
+ if (p)
+ p->postParentEvent(ev);
+ else
+ ok = false;
+ } else {
+ QScxml* session = QScxmlPrivate::sessions[target];
+ if (session) {
+ session->postEvent(ev);
+ } else
+ ok = false;
+ }
+ if (!ok)
+ scxml->postNamedEvent("error.targetunavailable");
+
+ } else {
+ scxml->postNamedEvent("error.send.typeinvalid");
+ }
+ }
+ }
+ return QScriptValue();
+}
+
+// scxml.invoke (type, target, paramNames, paramValues, content)
+static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine)
+{
+ QScxml* scxml = qobject_cast<QScxml*>(engine->globalObject().property("scxml.stateMachine").toQObject());
+ if (scxml) {
+ QString type,target;
+ QStringList pnames;
+ QVariantList pvals;
+ QScriptValue cnt;
+ if (context->argumentCount() > 0)
+ type = context->argument(0).toString();
+ if (type.isEmpty())
+ type = "scxml";
+ if (context->argumentCount() > 1)
+ target = context->argument(1).toString();
+ if (context->argumentCount() > 2)
+ qScriptValueToSequence<QStringList>(context->argument(2),pnames);
+ if (context->argumentCount() > 3) {
+ QScriptValueIterator it (context->argument(3));
+ while (it.hasNext()) {
+ it.next();
+ pvals.append(it.value().toVariant());
+ }
+ } if (context->argumentCount() > 4)
+ cnt = context->argument(4);
+
+
+
+ QScxmlInvokerFactory* invf = NULL;
+ for (int i=0; i < scxml->pvt->invokerFactories.count() && invf == NULL; ++i)
+ if (scxml->pvt->invokerFactories[i]->isTypeSupported(type))
+ invf = scxml->pvt->invokerFactories[i];
+ if (invf) {
+ QScxmlEvent* ev = new QScxmlEvent("",pnames,pvals,cnt);
+ ev->metaData.origin = scxml->baseUrl();
+ ev->metaData.target = target;
+ ev->metaData.targetType = type;
+ ev->metaData.originType = "scxml";
+ ev->metaData.kind = QScxmlEvent::MetaData::External;
+ QScxmlInvoker* inv = invf->createInvoker(ev,scxml);
+ if (inv)
+ inv->activate();
+ return engine->newQObject(inv);
+ } else {
+ scxml->postNamedEvent("error.invalidtargettype");
+ }
+
+ }
+ return QScriptValue();
+}
+
+
+static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine)
+{
+ QScxml* scxml = qobject_cast<QScxml*>(engine->globalObject().property("scxml.stateMachine").toQObject());
+ if (scxml) {
+ if (context->argumentCount() > 0) {
+ QString name = context->argument(0).toString();
+ if (!name.isEmpty()) {
+ QSet<QAbstractState*> cfg = scxml->configuration();
+ foreach (QAbstractState* st, cfg) {
+ if (st->objectName() == name)
+ return qScriptValueFromValue<bool>(engine,true);
+ }
+ }
+ }
+ }
+ return qScriptValueFromValue<bool>(engine,false);
+
+}
+
+};
+/*!
+ \class QScxmlEvent
+ \brief The QScxmlEvent class stands for a general named event with a list of parameter names and parameter values.
+
+ Encapsulates an event that conforms to the SCXML definition of events.
+
+ \ingroup sctools
+
+*/
+/*! \enum QScxmlEvent::MetaData::Kind
+
+ This enum specifies the kind (or context) of the event.
+ \value Platform An event coming from the itself, such as a script error.
+ \value Internal An event sent with a <raise> or <send target="_internal">.
+ \value External An event sent from an invoker, directly from C++, or from a <send target="scxml"> element.
+*/
+
+/*!
+ Returns the name of the event.
+ */
+ QString QScxmlEvent::eventName() const
+{
+ return ename;
+}
+ /*!
+ Return a list containing the parameter names.
+ */
+QStringList QScxmlEvent::paramNames () const
+{
+ return pnames;
+}
+ /*!
+ Return a list containing the parameter values.
+ */
+QVariantList QScxmlEvent::paramValues () const
+{
+ return pvals;
+}
+ /*!
+ Return a QtScript object that can be passed as an additional parameter.
+ */
+QScriptValue QScxmlEvent::content () const
+{
+ return cnt;
+}
+ /*!
+ Returns the parameter value equivalent to parameter \a name.
+ */
+QVariant QScxmlEvent::param (const QString & name) const
+{
+ int idx = pnames.indexOf(name);
+ if (idx >= 0)
+ return pvals[idx];
+ else
+ return QVariant();
+}
+/*!
+ Creates a QScxmlEvent named \a name, with parameter names \a paramNames, parameter values \a paramValues, and
+ a QtScript object \a content as an additional parameter.
+*/
+QScxmlEvent::QScxmlEvent(
+ const QString & name,
+ const QStringList & paramNames,
+ const QVariantList & paramValues,
+ const QScriptValue & content)
+
+ : QEvent(QScxmlEvent::eventType()),ename(name),pnames(paramNames),pvals(paramValues),cnt(content)
+{
+ metaData.kind = MetaData::Internal;
+}
+
+/*! \class QScxmlTransition
+ \brief The QScxmlTransition class stands for a transition that responds to QScxmlEvent, and can be made conditional with a \l conditionExpression.
+ Equivalent to the SCXML transition tag.
+
+ \ingroup sctools
+ */
+/*! \property QScxmlTransition::eventPrefix
+ The event prefix to be used when testing if the transition needs to be invoked.
+ Uses SCXML prefix matching. Use * to handle any event.
+ */
+/*! \property QScxmlTransition::conditionExpression
+ A QtScript expression that's evaluated to test whether the transition needs to be invoked.
+ */
+
+/*!
+ Creates a new QScxmlTransition from \a state, that uses \a machine to evaluate the conditions.
+ */
+QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine)
+ : QAbstractTransition(state),scxml(machine)
+{
+}
+
+/*!
+ \internal
+ */
+bool QScxmlTransition::eventTest(QEvent *e)
+{
+ QScriptEngine* engine = scxml->scriptEngine();
+ QString ev;
+
+ if (e) {
+ if (e->type() == QScxmlEvent::eventType()) {
+ ev = ((QScxmlEvent*)e)->eventName();
+ }
+ if (!(eventPrefix() == "*" || eventPrefix() == ev || ev.startsWith(eventPrefix()+".")))
+ return false;
+ }
+
+
+ if (!conditionExpression().isEmpty()) {
+
+ QScriptValue v = engine->evaluate(conditionExpression(),scxml->baseUrl().toLocalFile());
+ if (engine->hasUncaughtException()) {
+
+ qDebug() << engine->uncaughtException().toString();
+ QScxmlEvent* e = new QScxmlEvent("error.illegalcond",
+ QStringList()<< "error" << "expr" << "line" << "backtrace",
+ QVariantList()
+ << QVariant(engine->uncaughtException().toString())
+ << QVariant(conditionExpression())
+ << QVariant(engine->uncaughtExceptionLineNumber())
+ << QVariant(engine->uncaughtExceptionBacktrace()));
+ e->metaData.kind = QScxmlEvent::MetaData::Platform;
+ scxml->postEvent(e);
+ engine->clearExceptions();
+ return false;
+ }
+ return v.toBoolean();
+ }
+
+ return true;
+}
+
+class QScxmlDefaultInvoker : public QScxmlInvoker
+{
+ Q_OBJECT
+
+
+ public:
+ QScxmlDefaultInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),cancelled(false),childSm(0)
+ {
+ childSm = QScxml::load (ievent->metaData.origin.resolved(ievent->metaData.target).toLocalFile(),this);
+ if (childSm == NULL) {
+ postParentEvent("error.targetunavailable");
+ } else {
+ connect(childSm,SIGNAL(finished()),this,SLOT(deleteLater()));
+
+ }
+ }
+
+
+
+ static void initInvokerFactory(QScxml*) {}
+
+ static bool isTypeSupported(const QString & t) { return t.isEmpty() || t.toLower() == "scxml"; }
+
+ public Q_SLOTS:
+ void activate ()
+ {
+ if (childSm)
+ childSm->start();
+ }
+
+ void cancel ()
+ {
+ cancelled = true;
+ if (childSm)
+ childSm->stop();
+
+ }
+
+ private:
+ bool cancelled;
+ QScxml* childSm;
+};
+class QScxmlBindingInvoker : public QScxmlInvoker
+{
+ Q_OBJECT
+ QScriptValue content;
+ QScriptValue stored;
+
+ public:
+ QScxmlBindingInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p)
+ {
+ }
+
+ static void initInvokerFactory(QScxml*) {}
+
+ static bool isTypeSupported(const QString & t) { return t.toLower() == "q-bindings"; }
+
+ public Q_SLOTS:
+ void activate ()
+ {
+ QScriptEngine* engine = ((QScxml*)parent())->scriptEngine();
+ QScriptValue content = initEvent->content();
+ if (content.isArray()) {
+ stored = content.engine()->newArray(content.property("length").toInt32());
+
+ QScriptValueIterator it (content);
+ for (int i=0; it.hasNext(); ++i) {
+ it.next();
+ if (it.value().isArray()) {
+ QScriptValue object = it.value().property(0);
+ QString property = it.value().property(1).toString();
+ QScriptValue val = it.value().property(2);
+ QScriptValue arr = engine->newArray(3);
+ arr.setProperty("0",it.value().property(0));
+ arr.setProperty("1",it.value().property(1));
+ if (object.isQObject()) {
+ QObject* o = object.toQObject();
+ arr.setProperty("2",engine->newVariant(o->property(property.toAscii().constData())));
+ o->setProperty(property.toAscii().constData(),val.toVariant());
+ } else if (object.isObject()) {
+ arr.setProperty("2",object.property(property));
+ object.setProperty(property,val);
+ }
+ stored.setProperty(i,arr);
+ }
+ }
+ }
+ }
+
+ void cancel ()
+ {
+ if (stored.isArray()) {
+ QScriptValueIterator it (stored);
+ while (it.hasNext()) {
+ it.next();
+ if (it.value().isArray()) {
+ QScriptValue object = it.value().property(0);
+ QString property = it.value().property(1).toString();
+ QScriptValue val = it.value().property(2);
+ if (object.isQObject()) {
+ QObject* o = object.toQObject();
+ o->setProperty(property.toAscii().constData(),val.toVariant());
+ } else if (object.isObject()) {
+ object.setProperty(property,val);
+ }
+ }
+ }
+ }
+ }
+};
+
+/*!
+\fn QScxmlInvoker::~QScxmlInvoker()
+*/
+
+
+/*!
+\fn QScxml::eventTriggered(const QString & name)
+
+This signal is emitted when external event \a name is handled in the state machine.
+*/
+
+/*!
+ Creates a new QScxml object, with parent \a parent.
+ */
+
+QScxml::QScxml(QObject* parent)
+ : QStateMachine(parent)
+{
+ pvt = new QScxmlPrivate;
+ pvt->scriptEng = new QScriptEngine(this);
+ QScriptValue glob = pvt->scriptEng->globalObject();
+ QScriptValue utilObj = pvt->scriptEng->newObject();
+ glob.setProperty("In",pvt->scriptEng->newFunction(QScxmlFunctions::isInState));
+ glob.setProperty("_rcvSig",pvt->scriptEng->newFunction(QScxmlFunctions::receiveSignal));
+ glob.setProperty("print",pvt->scriptEng->newFunction(QScxmlFunctions::script_print));
+ utilObj.setProperty("postEvent",pvt->scriptEng->newFunction(QScxmlFunctions::postEvent));
+ utilObj.setProperty("invoke",pvt->scriptEng->newFunction(QScxmlFunctions::invoke));
+ utilObj.setProperty("cssTime",pvt->scriptEng->newFunction(QScxmlFunctions::cssTime));
+ utilObj.setProperty("stateMachine",pvt->scriptEng->newQObject(this));
+ utilObj.setProperty("clone",pvt->scriptEng->newFunction(QScxmlFunctions::deepCopy));
+ utilObj.setProperty("setTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::setTimeout));
+ utilObj.setProperty("clearTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::clearTimeout));
+ QScriptValue dmObj = pvt->scriptEng->newObject();
+ glob.setProperty("_data",pvt->scriptEng->newObject());
+ glob.setProperty("_global",pvt->scriptEng->globalObject());
+ glob.setProperty("scxml",utilObj);
+ glob.setProperty("connectSignalToEvent",pvt->scriptEng->evaluate("function(sig,ev) {sig.connect({'e':ev},_rcvSig);}"));
+ static QScxmlAutoInvokerFactory<QScxmlDefaultInvoker> _s_defaultInvokerFactory;
+ static QScxmlAutoInvokerFactory<QScxmlBindingInvoker> _s_bindingInvokerFactory;
+ registerInvokerFactory(&_s_defaultInvokerFactory);
+ registerInvokerFactory(&_s_bindingInvokerFactory);
+ connect(this,SIGNAL(started()),this,SLOT(registerSession()));
+ connect(this,SIGNAL(stopped()),this,SLOT(unregisterSession()));
+#ifdef QT_GUI_LIB
+ static QScxmlAutoInvokerFactory<QScxmlMenuInvoker> _s_msgboxInvokerFactory;
+ static QScxmlAutoInvokerFactory<QScxmlMessageBoxInvoker> _s_menuInvokerFactory;
+ registerInvokerFactory(&_s_msgboxInvokerFactory);
+ registerInvokerFactory(&_s_menuInvokerFactory);
+#endif
+}
+
+/*! \internal */
+void QScxml::beginSelectTransitions(QEvent* ev)
+{
+ QScriptValue eventObj = pvt->scriptEng->newObject();
+ if (ev) {
+ if (ev->type() == QScxmlEvent::eventType()) {
+ QScxmlEvent* se = (QScxmlEvent*)ev;
+ eventObj.setProperty("name",qScriptValueFromValue<QString>(pvt->scriptEng,se->eventName()));
+ eventObj.setProperty("target",qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue<QUrl>(se->metaData.target)));
+ eventObj.setProperty("targettype",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.targetType));
+ eventObj.setProperty("invokeid",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.invokeID));
+ eventObj.setProperty("origin",QScriptValue(qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue<QUrl>(se->metaData.origin))));
+ eventObj.setProperty("originType",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.originType));
+ switch (se->metaData.kind) {
+ case QScxmlEvent::MetaData::Internal:
+ eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "internal"));
+ break;
+ case QScxmlEvent::MetaData::External:
+ eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "external"));
+ break;
+ case QScxmlEvent::MetaData::Platform:
+ eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "platform"));
+ default:
+ break;
+
+ }
+
+ QScriptValue dataObj = pvt->scriptEng->newObject();
+ int i=0;
+ foreach (QString s, se->paramNames()) {
+ QScriptValue v = qScriptValueFromValue(pvt->scriptEng, se->paramValues()[i]);
+ dataObj.setProperty(QString::number(i),v);
+ dataObj.setProperty(s,v);
+ ++i;
+ }
+ eventObj.setProperty("data",dataObj);
+ emit eventTriggered(se->eventName());
+ }
+ }
+ scriptEngine()->globalObject().setProperty("_event",eventObj);
+
+ QHash<QString,QAbstractState*> curTargets;
+
+ for (int i = pvt->snapshotStack.size()-1; i >= 0 && curTargets.size() < pvt->anchorTransitions.keys().size(); --i) {
+ if (!curTargets.contains(pvt->snapshotStack.at(i).anchorType)) {
+ curTargets[pvt->snapshotStack.at(i).anchorType] = pvt->snapshotStack.at(i).state;
+ }
+ }
+ for (QMultiHash<QString,QAbstractTransition*>::const_iterator it = pvt->anchorTransitions.constBegin(); it != pvt->anchorTransitions.constEnd(); ++it) {
+ it.value()->setTargetState(curTargets[it.key()]);
+ }
+
+}
+
+/*! \internal */
+void QScxml::endMicrostep(QEvent*)
+{
+ scriptEngine()->globalObject().setProperty("_event",QScriptValue());
+ for (QHash<QString,QScxmlPrivate::AnchorSnapshot>::iterator
+ it = pvt->curSnapshot.begin();
+ it != pvt->curSnapshot.end(); ++it) {
+
+ pvt->snapshotStack.push(it.value());
+
+ }
+ if (pvt->snapshotStack.size() > QScxmlPrivate::MaxSnapshots) {
+ pvt->snapshotStack.remove(0,pvt->snapshotStack.size()-100);
+ }
+ pvt->curSnapshot.clear();
+}
+
+/*! Returns the script engine attached to the state-machine. */
+QScriptEngine* QScxml::scriptEngine () const
+{
+ return pvt->scriptEng;
+}
+
+/*!
+ Registers object \a o to the script engine attached to the state machine.
+ The object can be accessible from global variable \a name. If \a name is not provided,
+ the object's name is used. If \a recursive is true, all the object's decendants are registered
+ as global objects, with their respective object names as variable names.
+*/
+void QScxml::registerObject (QObject* o, const QString & name, bool recursive)
+{
+ QString n(name);
+ if (n.isEmpty())
+ n = o->objectName();
+ if (!n.isEmpty())
+ pvt->scriptEng->globalObject().setProperty(n,pvt->scriptEng->newQObject(o));
+ if (recursive) {
+ QObjectList ol = o->findChildren<QObject*>();
+ foreach (QObject* oo, ol) {
+ if (!oo->objectName().isEmpty())
+ registerObject(oo);
+ }
+ }
+}
+
+/*!
+ Posts a QScxmlEvent named \a event, with no payload.
+ \sa QScxmlEvent
+ */
+void QScxml::postNamedEvent(const QString & event)
+{
+ QScxmlEvent* e = new QScxmlEvent(event);
+ e->metaData.kind = QScxmlEvent::MetaData::External;
+ postEvent(e);
+}
+/*!
+ Executes script \a s in the attached script engine.
+ If the script fails, a "error.illegalvalue" event is posted to the state machine.
+*/
+
+void QScxml::executeScript (const QString & s)
+{
+ pvt->scriptEng->evaluate (s,baseUrl().toLocalFile());
+ if (pvt->scriptEng->hasUncaughtException()) {
+ QScxmlEvent* e = new QScxmlEvent("error.illegalvalue",
+ QStringList()<< "error" << "expr" << "line" << "backtrace",
+ QVariantList()
+ << QVariant(pvt->scriptEng->uncaughtException().toString())
+ << QVariant(s)
+ << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber())
+ << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace()));
+ e->metaData.kind = QScxmlEvent::MetaData::Platform;
+ postEvent(e);
+ pvt->scriptEng->clearExceptions();
+ }
+}
+
+/*!
+ Enabled invoker factory \a f to be called from <invoke /> tags.
+ */
+
+void QScxml::registerInvokerFactory (QScxmlInvokerFactory* f)
+{
+ pvt->invokerFactories << f;
+ f->init(this);
+}
+
+/*! \class QScxmlInvoker
+ \brief The QScxmlInvoker class an invoker, which the state-machine context can activate or cancel
+ with an <invoke> tag.
+
+ \ingroup sctools
+
+ An invoker is a object that represents an external component that the state machine
+ can activate when the encompassing state is entered, or cancel when the encompassing
+ state is exited from.
+ */
+
+/*! \fn QScxmlInvoker::QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* parent)
+ When reimplementing the constructor, always use the two parameters (\a ievent and \a parent),
+ as they're called from QScxmlInvokerFactory.
+*/
+
+/*! \fn QScxmlInvoker::activate()
+ This function is called when the encompassing state is entered.
+ The call to this function from the state-machine context is asynchronous, to make sure
+ that the state is not exited during the same step in which it's entered.
+
+*/
+
+/*! \fn QScxmlInvoker::cancel()
+ Reimplement this function to allow for asynchronous cancellation of the invoker.
+ It's the invoker's responsibility to delete itself after this function has been called.
+ The default implementation deletes the invoker.
+*/
+
+/*! \fn QScxml* QScxmlInvoker::parentStateMachine()
+ Returns the state machine encompassing the invoker.
+ */
+
+/*!
+ Posts an event \a e to the state machine encompassing the invoker.
+ */
+void QScxmlInvoker::postParentEvent (QScxmlEvent* e)
+{
+ e->metaData.origin = initEvent->metaData.target;
+ e->metaData.target = initEvent->metaData.origin;
+ e->metaData.originType = initEvent->metaData.targetType;
+ e->metaData.targetType = initEvent->metaData.originType;
+ e->metaData.kind = QScxmlEvent::MetaData::External;
+ e->metaData.invokeID = initEvent->metaData.invokeID;
+ parentStateMachine()->postEvent(e);
+}
+/*! \overload
+ Posts a QScxmlEvent named \a e to the encompassing state machine.
+ */
+void QScxmlInvoker::postParentEvent(const QString & e)
+{
+ QScxmlEvent* ev = new QScxmlEvent(e);
+ ev->metaData.kind = QScxmlEvent::MetaData::External;
+ postParentEvent(ev);
+}
+/*! \internal */
+QScxml::~QScxml()
+{
+ delete pvt;
+}
+
+QString QScxmlInvoker::id () const
+{
+ return initEvent->metaData.invokeID;
+}
+void QScxmlInvoker::setID(const QString & id)
+{
+ initEvent->metaData.invokeID = id;
+}
+
+QScxmlInvoker::~QScxmlInvoker()
+{
+ if (cancelled)
+ postParentEvent("CancelResponse");
+ else
+ postParentEvent(QString("done.invoke.%1").arg(initEvent->metaData.invokeID));
+}
+/*!
+ \property QScxml::baseUrl
+ The url used to resolve scripts and invoke urls.
+*/
+QUrl QScxml::baseUrl() const
+{
+ return pvt->burl;
+}
+
+void QScxml::setBaseUrl(const QUrl & u)
+{
+ pvt->burl = u;
+}
+
+void QScxml::registerSession()
+{
+ pvt->sessionID = QUuid::createUuid().toString();
+ pvt->sessions[pvt->sessionID] = this;
+ pvt->scriptEng->globalObject().setProperty("_sessionid",qScriptValueFromValue<QString>(scriptEngine(), pvt->sessionID));
+ executeScript(pvt->startScript);
+}
+
+void QScxml::unregisterSession()
+{
+ pvt->scriptEng->globalObject().setProperty("_sessionid",QScriptValue());
+ pvt->sessions.remove(pvt->sessionID);
+}
+
+/*!
+ Returns a statically-generated event type to be used by SCXML events.
+*/
+QEvent::Type QScxmlEvent::eventType()
+{
+ static QEvent::Type _t = (QEvent::Type)QEvent::registerEventType(QEvent::User+200);
+ return _t;
+}
+const char SCXML_NAMESPACE [] = "http://www.w3.org/2005/07/scxml";
+
+
+
+struct ScTransitionInfo
+{
+
+ QScxmlTransition* transition;
+ QStringList targets;
+ QString anchor;
+ QString script;
+ ScTransitionInfo() : transition(NULL) {}
+};
+
+class QScxmlScriptExec : public QObject
+{
+ Q_OBJECT
+ QString script;
+ QScxml* scxml;
+ public:
+ QScxmlScriptExec(const QString & scr, QScxml* scx) : script(scr),scxml(scx)
+ {
+ }
+ public Q_SLOTS:
+ void exec()
+ {
+ scxml->executeScript(script);
+ }
+};
+
+struct ScStateInfo
+{
+ QString initial;
+};
+
+struct ScHistoryInfo
+{
+ QHistoryState* hstate;
+ QString defaultStateID;
+};
+
+struct ScExecContext
+{
+ QScxml* sm;
+ QString script;
+ enum {None, StateEntry,StateExit,Transition } type;
+ QScxmlTransition* trans;
+ QAbstractState* state;
+ ScExecContext() : sm(NULL),type(None),trans(NULL),state(NULL)
+ {
+ }
+
+ void applyScript()
+ {
+ if (!script.isEmpty()) {
+ QScxmlScriptExec* exec = new QScxmlScriptExec(script,sm);
+ switch(type) {
+ case StateEntry:
+ QObject::connect(state,SIGNAL(entered()),exec,SLOT(exec()));
+ break;
+ case StateExit:
+ QObject::connect(state,SIGNAL(exited()),exec,SLOT(exec()));
+ break;
+ case Transition:
+ QObject::connect(trans,SIGNAL(activated()),exec,SLOT(exec()));
+ break;
+ default:
+ delete exec;
+ break;
+ }
+ }
+ }
+};
+
+class QScxmlLoader
+{
+ public:
+ QScxml* stateMachine;
+
+ QList<ScTransitionInfo> transitions;
+ QHash<QState*,ScStateInfo> stateInfo;
+ QList<ScHistoryInfo> historyInfo;
+ QHash<QString,QAbstractState*> stateByID;
+ QSet<QString> signalEvents;
+ QSet<QState*> statesWithFinal;
+ void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename);
+ QScxml* load (QIODevice* device, QObject* obj = NULL, const QString & filename = "");
+
+ QScriptValue evaluateFile (const QString & fn)
+ {
+ QFile f (fn);
+ f.open(QIODevice::ReadOnly);
+ return stateMachine->scriptEngine()->evaluate(QString::fromUtf8(f.readAll()),fn);
+ }
+};
+
+class QScxmlAnchorSave : public QObject
+{
+ Q_OBJECT
+ public:
+ QScxml* sm;
+ QScxmlPrivate* pvt;
+ QScxmlPrivate::AnchorSnapshot anchorSnapshot;
+ QScxmlAnchorSave(QScxml* p,QScxmlPrivate* pv,
+ const QString & type, const QString & loc,
+ QAbstractState* s) :
+ QObject(p),sm(p),pvt(pv)
+ {
+ anchorSnapshot.anchorType = type;
+ anchorSnapshot.location = loc;
+ anchorSnapshot.state = s;
+ }
+
+ public Q_SLOTS:
+
+ void save()
+ {
+ if (!anchorSnapshot.location.isEmpty()) {
+ anchorSnapshot.snapshot = _q_deepCopy(sm->scriptEngine()->evaluate(anchorSnapshot.location));
+ }
+ pvt->curSnapshot[anchorSnapshot.anchorType] = anchorSnapshot;
+ }
+};
+
+class QScxmlAnchorRestore : public QObject
+{
+ Q_OBJECT
+ public:
+ QScxml* sm;
+ QScxmlPrivate* pvt;
+ QString anchorType;
+ QScxmlAnchorRestore(QScxml* p,QScxmlPrivate* pv,
+ const QString & type) :
+ QObject(p),sm(p),pvt(pv),anchorType(type)
+ {
+
+ }
+
+ public Q_SLOTS:
+ void restore ()
+ {
+ pvt->curSnapshot.clear();
+ while (!pvt->snapshotStack.isEmpty()) {
+ QScxmlPrivate::AnchorSnapshot s = pvt->snapshotStack.pop();
+ if (s.anchorType == anchorType) {
+ if (s.location != "") {
+ sm->scriptEngine()->globalObject().setProperty("_snapshot",s.snapshot);
+ sm->scriptEngine()->evaluate(QString ("%1 = _snapshot;").arg(s.location));
+ sm->scriptEngine()->globalObject().setProperty("_snapshot",QScriptValue());
+ }
+ break;
+ }
+ }
+ }
+};
+
+static QString sanitize (const QString & str)
+{
+ return str;
+// return QString("eval(unescape(\"%1\"))").
+// arg(QString::fromAscii(str.trimmed().toUtf8().toPercentEncoding(QByteArray("[]()<>;:#/'`_-., \t@!^&*{}"))));
+}
+static QString sanitize (const QStringRef & str)
+{
+ return sanitize(str.toString());
+}
+
+void QScxmlLoader::loadState (
+ QState* stateParam,
+ QIODevice *dev,
+ const QString & stateID,
+ const QString & filename)
+{
+ QXmlStreamReader r (dev);
+ QState* curState = NULL;
+ ScExecContext curExecContext;
+ curExecContext.sm = stateMachine;
+ QState* topLevelState = NULL;
+ QHistoryState* curHistoryState = NULL;
+ QString initialID = "";
+ QString idLocation;
+ QScxmlTransition* curTransition = NULL;
+ bool inRoot = true;
+ while (!r.atEnd()) {
+ r.readNext();
+ if (r.hasError()) {
+ qDebug() << QString("SCXML read error at line %1, column %2: %3").arg(r.lineNumber()).arg(r. columnNumber()).arg(r.errorString());
+ return;
+ }
+ if (r.namespaceUri() == SCXML_NAMESPACE || r.namespaceUri() == "") {
+ if (r.isStartElement()) {
+ if (r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) {
+ if (stateID == "") {
+ topLevelState = curState = stateParam;
+ stateInfo[curState].initial = r.attributes().value("initial").toString();
+ if (curState == stateMachine->rootState()) {
+ stateMachine->scriptEngine()->globalObject().setProperty("_name",qScriptValueFromValue<QString>(stateMachine->scriptEngine(),r.attributes().value("name").toString()));
+ }
+
+ }
+ } else if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) {
+ inRoot = false;
+ QString id = r.attributes().value("id").toString();
+ QState* newState = NULL;
+ if (curState) {
+ newState= new QState(r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0 ? QState::ParallelStates : QState::ExclusiveStates,
+ curState);
+ } else if (id == stateID) {
+ topLevelState = newState = stateParam;
+
+ }
+ if (newState) {
+ stateInfo[newState].initial = r.attributes().value("initial").toString();
+ newState->setObjectName(id);
+ if (!id.isEmpty() && stateInfo[curState].initial == id) {
+
+ if (curState == stateMachine->rootState())
+ stateMachine->setInitialState(newState);
+ else
+ curState->setInitialState(newState);
+ }
+ QString src = r.attributes().value("src").toString();
+ if (!src.isEmpty()) {
+ int refidx = src.indexOf('#');
+ QString srcfile, refid;
+ if (refidx > 0) {
+ srcfile = src.left(refidx);
+ refid = src.mid(refidx+1);
+ } else
+ srcfile = src;
+ srcfile = QDir::cleanPath( QFileInfo(filename).dir().absoluteFilePath(srcfile));
+ QFile newFile (srcfile);
+ if (newFile.exists()) {
+ newFile.open(QIODevice::ReadOnly);
+ loadState(newState,&newFile,refid,srcfile);
+ }
+ }
+ initialID = r.attributes().value("initial").toString();
+ stateByID[id] = newState;
+ curState = newState;
+ curExecContext.state = newState;
+ }
+
+ } else if (r.name().toString().compare("initial",Qt::CaseInsensitive) == 0) {
+ if (curState && stateInfo[curState].initial == "") {
+ QState* newState = new QState(curState);
+ curState->setInitialState(newState);
+ }
+ } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) {
+ if (curState) {
+ QString id = r.attributes().value("id").toString();
+ curHistoryState = new QHistoryState(r.attributes().value("type") == "shallow" ? QHistoryState::ShallowHistory : QHistoryState::DeepHistory,curState);
+ curHistoryState->setObjectName(id);
+ stateByID[id] = curHistoryState;
+ }
+ } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) {
+ if (curState) {
+ QString id = r.attributes().value("id").toString();
+ QFinalState* f = new QFinalState(curState);
+ f->setObjectName(id);
+ curExecContext.state = f;
+ statesWithFinal.insert(curState);
+ QState* gp = qobject_cast<QState*>(curState->parentState());
+ if (gp->childMode() == QState::ParallelStates) {
+ statesWithFinal.insert(gp);
+ }
+ stateByID[id] = f;
+ }
+ } else if (r.name().toString().compare("script",Qt::CaseInsensitive) == 0) {
+ QString txt = r.readElementText().trimmed();
+ if (curExecContext.type == ScExecContext::None && curState == topLevelState) {
+ stateMachine->executeScript(txt);
+ } else
+ curExecContext.script += txt;
+ } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) {
+ curExecContext.script +=
+ QString("print('[' + %1 + '][' + %2 + ']' + %3);")
+ .arg(sanitize(r.attributes().value("label")))
+ .arg(sanitize(r.attributes().value("level")))
+ .arg(sanitize(r.attributes().value("expr")));
+
+ } else if (r.name().toString().compare("assign",Qt::CaseInsensitive) == 0) {
+ QString locattr = r.attributes().value("location").toString();
+ if (locattr.isEmpty()) {
+ locattr = r.attributes().value("dataid").toString();
+ if (!locattr.isEmpty())
+ locattr = "_data." + locattr;
+ }
+ if (!locattr.isEmpty()) {
+ curExecContext.script += QString ("%1 = %2;").arg(locattr).arg(sanitize(r.attributes().value("expr")));
+ }
+ } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += QString("if (%1) {").arg(sanitize(r.attributes().value("cond")));
+ } else if (r.name().toString().compare("elseif",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += QString("} elseif (%1) {").arg(sanitize(r.attributes().value("cond")));
+ } else if (r.name().toString().compare("else",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += " } else { ";
+ } else if (r.name().toString().compare("cancel",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += QString("scxml.clearTimeout (%1);").arg(sanitize(r.attributes().value("id")));
+ } else if (r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0) {
+ curExecContext.type = ScExecContext::StateEntry;
+ curExecContext.script = "";
+ } else if (r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0) {
+ curExecContext.type = ScExecContext::StateExit;
+ curExecContext.script = "";
+ } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0 || r.name().toString().compare("event",Qt::CaseInsensitive) == 0 ) {
+ QString ev = r.attributes().value("event").toString();
+ if (ev.isEmpty())
+ ev = r.attributes().value("name").toString();
+ curExecContext.script +=
+ QString("{"
+ "var paramNames = []; var paramValues = []; "
+ "var content = ''; var eventName='%1'; "
+ "var target = '_internal'; var targetType = 'scxml'; ").arg(ev);
+
+ } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) {
+ QString type = r.attributes().value("type").toString();
+ if (type.isEmpty())
+ type = r.attributes().value("targettype").toString();
+ curExecContext.script +=
+ QString("{"
+ "var paramNames = [%1]; var paramValues = []; "
+ "var content = ''; var eventName=%2; "
+ "var targetType = %3; var target = %4;")
+ .arg(r.attributes().value("namelist").toString().replace(" ",","))
+ .arg(sanitize(r.attributes().value("event").toString()))
+ .arg(type.isEmpty() ? "'scxml'" : sanitize(r.attributes().value("type")))
+ .arg(r.attributes().value("target").length() ? sanitize(r.attributes().value("target")) : "''");
+ idLocation = r.attributes().value("idlocation").toString();
+ if (idLocation.isEmpty())
+ idLocation = r.attributes().value("sendid").toString();
+
+ curExecContext.script += QString("var delay = %1; ").arg(r.attributes().value("delay").length()
+ ? QString("scxml.cssTime(%1)").arg(sanitize(r.attributes().value("delay")))
+ : "0");
+ } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) {
+ idLocation = r.attributes().value("idlocation").toString();
+ if (idLocation.isEmpty())
+ idLocation = r.attributes().value("invokeid").toString();
+ QObject::connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec()));
+
+ QString type = r.attributes().value("type").toString();
+ if (type.isEmpty())
+ type = r.attributes().value("targettype").toString();
+ curExecContext.type = ScExecContext::StateEntry;
+ curExecContext.state = curState;
+ curExecContext.script =
+ QString("{"
+ "var paramNames = []; var paramValues = []; "
+ "var content = ''; "
+ "var srcType = \"%1\"; var src = %2;")
+ .arg(type.length() ? type : "scxml")
+ .arg(r.attributes().value("src").length() ? sanitize(r.attributes().value("target")) : "\"\"");
+
+
+ } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) {
+ if (curHistoryState) {
+ ScHistoryInfo inf;
+ inf.hstate = curHistoryState;
+ inf.defaultStateID = r.attributes().value("target").toString();
+ historyInfo.append(inf);
+ } else {
+ ScTransitionInfo inf;
+ inf.targets = r.attributes().value("target").toString().split(' ');
+ curExecContext.type = ScExecContext::Transition;
+ curExecContext.script = "";
+ curTransition = new QScxmlTransition(curState,stateMachine);
+ curTransition->setConditionExpression(r.attributes().value("cond").toString());
+ curTransition->setEventPrefix(r.attributes().value("event").toString());
+ curExecContext.trans = curTransition;
+ QString anc = r.attributes().value("anchor").toString();
+ if (!anc.isEmpty()) {
+ stateMachine->pvt->anchorTransitions.insert(anc,curTransition);
+ QObject::connect (curTransition, SIGNAL(activated()),new QScxmlAnchorRestore(stateMachine,stateMachine->pvt,anc),SLOT(restore()));
+ }
+ inf.transition = curTransition;
+ transitions.append(inf);
+ if (curTransition->eventPrefix().startsWith("q-signal:")) {
+ signalEvents.insert(curTransition->eventPrefix());
+ }
+ curTransition->setObjectName(QString ("%1 to %2 on %3 if %4 (anchor=%5)").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefix()).arg(curTransition->conditionExpression()).arg(anc));
+ }
+ } else if (r.name().toString().compare("anchor",Qt::CaseInsensitive) == 0) {
+ QObject::connect(curState,SIGNAL(exited()),new QScxmlAnchorSave(stateMachine,stateMachine->pvt,r.attributes().value("type").toString(),r.attributes().value("snapshot").toString(),curState),SLOT(save()));
+ } else if (r.name().toString().compare("data",Qt::CaseInsensitive) == 0) {
+ QScriptValue val = qScriptValueFromValue<QString>(stateMachine->scriptEngine(),"") ;
+ QString id = r.attributes().value("id").toString();
+ if (r.attributes().value("src").length())
+ val = evaluateFile(QFileInfo(filename).dir().absoluteFilePath(r.attributes().value("src").toString()));
+ else {
+ if (r.attributes().value("expr").length()) {
+ val = stateMachine->scriptEngine()->evaluate(r.attributes().value("expr").toString());
+ } else {
+ QString t = r.readElementText();
+ if (!t.isEmpty())
+ val = stateMachine->scriptEngine()->evaluate(t);
+ }
+ }
+ stateMachine->scriptEngine()->evaluate("_data")
+ .setProperty(id,val);
+ } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) {
+ curExecContext.script +=
+ QString("paramNames[paramNames.length] = \"%1\";")
+ .arg(r.attributes().value("name").toString());
+ curExecContext.script +=
+ QString("paramValues[paramValues.length] = %1;")
+ .arg(sanitize(r.attributes().value("expr")));
+
+ } else if (r.name().toString().compare("content",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += QString("content = %1; ").arg(sanitize(r.readElementText()));
+ }
+ } else if (r.isEndElement()) {
+ if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) {
+ if (curState == topLevelState) {
+ return;
+ } else {
+ curState = qobject_cast<QState*>(curState->parent());
+ curExecContext.state = curState;
+ }
+ } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) {
+ curHistoryState = NULL;
+ } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) {
+ curExecContext.state = (curExecContext.state->parentState());
+ } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) {
+ if (!idLocation.isEmpty())
+ curExecContext.script += idLocation + " = ";
+ curExecContext.script += QString("scxml.setTimeout(function() { "
+ "scxml.postEvent("
+ "eventName,target,targetType,paramNames,paramValues,content"
+ ");"
+ "}, delay); }");
+ idLocation = "";
+ } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += "scxml.postEvent(eventName,target,targetType,paramNames,paramValues,content); }";
+ } else if (
+ r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0
+ || r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0
+ || r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) {
+ curExecContext.state = curState;
+ curExecContext.type = r.name().toString().compare("onexit",Qt::CaseInsensitive)==0 ? ScExecContext::StateExit : ScExecContext::StateEntry;
+ curExecContext.applyScript();
+ curExecContext.type = ScExecContext::None;
+ } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) {
+ if (!curHistoryState) {
+ curExecContext.trans = curTransition;
+ curExecContext.type = ScExecContext::Transition;
+ curExecContext.applyScript();
+ }
+
+ ScTransitionInfo* ti = &(transitions.last());
+ if (!curExecContext.script.isEmpty() && ti->anchor != "")
+ ti->script = curExecContext.script;
+ curExecContext.type = ScExecContext::None;
+ } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) {
+ curExecContext.script += QString("invoke_%1 = scxml.invoke(srcType,src,paramNames,paramValues,content); }").arg(curState->objectName());
+ if (!idLocation.isEmpty()) {
+ curExecContext.script += QString("%1 = invoke_%2;").arg(idLocation).arg(curState->objectName());
+ }
+ curExecContext.state = curState;
+ curExecContext.type = ScExecContext::StateEntry;
+ curExecContext.applyScript();
+ idLocation = "";
+ curExecContext.type = ScExecContext::None;
+ }
+ }
+ }
+ }
+}
+
+
+QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename)
+{
+ stateMachine = new QScxml(obj);
+ // traverse through the states
+ loadState(stateMachine->rootState(),device,"",filename);
+
+ // resolve history default state
+ foreach (ScHistoryInfo h, historyInfo) {
+ h.hstate->setDefaultState(stateByID[h.defaultStateID]);
+ }
+ foreach (QString s, signalEvents) {
+ QString sig = s;
+ sig = sig.mid(sig.indexOf(':')+1);
+ sig = sig.left(sig.indexOf('('));
+ QString scr = QString("%1.connect({e:\"%2\"},_rcvSig);\n").arg(sig).arg(s);
+ stateMachine->pvt->startScript += scr;
+ }
+
+ foreach (QState* s, statesWithFinal) {
+ QObject::connect(s,SIGNAL(finished()),stateMachine,SLOT(handleStateFinished()));
+ }
+
+ // resolve transitions
+
+ foreach (ScTransitionInfo t, transitions) {
+ QList<QAbstractState*> states;
+ if (!t.targets.isEmpty()) {
+ foreach (QString s, t.targets) {
+ if (!s.trimmed().isEmpty()) {
+ QAbstractState* st = stateByID[s];
+ if (st)
+ states.append(st);
+ }
+ }
+ t.transition->setTargetStates(states);
+ }
+ }
+
+ return stateMachine;
+}
+
+void QScxml::handleStateFinished()
+{
+ QState* state = qobject_cast<QState*>(sender());
+ if (state) {
+ postEvent(new QScxmlEvent("done.state." + state->objectName()));
+ }
+}
+
+/*!
+ Loads a state machine from an scxml file located at \a filename, with parent object \a o.
+ */
+QScxml* QScxml::load (const QString & filename, QObject* o)
+{
+ QScxmlLoader l;
+ QFile f (filename);
+ f.open(QIODevice::ReadOnly);
+ return l.load(&f,o,filename);
+}
+
+#include "qscxml.moc"