summaryrefslogtreecommitdiffstats
path: root/src/qscxml/scxmlstatetable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qscxml/scxmlstatetable.cpp')
-rw-r--r--src/qscxml/scxmlstatetable.cpp1010
1 files changed, 1010 insertions, 0 deletions
diff --git a/src/qscxml/scxmlstatetable.cpp b/src/qscxml/scxmlstatetable.cpp
new file mode 100644
index 0000000..5ffa56a
--- /dev/null
+++ b/src/qscxml/scxmlstatetable.cpp
@@ -0,0 +1,1010 @@
+/****************************************************************************
+ **
+ ** Copyright (c) 2015 Digia Plc
+ ** For any questions to Digia, please use contact form at http://qt.digia.com/
+ **
+ ** All Rights Reserved.
+ **
+ ** NOTICE: All information contained herein is, and remains
+ ** the property of Digia Plc and its suppliers,
+ ** if any. The intellectual and technical concepts contained
+ ** herein are proprietary to Digia Plc
+ ** and its suppliers and may be covered by Finnish and Foreign Patents,
+ ** patents in process, and are protected by trade secret or copyright law.
+ ** Dissemination of this information or reproduction of this material
+ ** is strictly forbidden unless prior written permission is obtained
+ ** from Digia Plc.
+ ****************************************************************************/
+
+#include "scxmlstatetable_p.h"
+#include "executablecontent_p.h"
+#include "scxmlevent_p.h"
+
+#include <QAbstractState>
+#include <QAbstractTransition>
+#include <QState>
+#include <QHash>
+#include <QString>
+#include <QTimer>
+#include <QLoggingCategory>
+#include <QJSEngine>
+#include <QtCore/private/qstatemachine_p.h>
+
+namespace Scxml {
+Q_LOGGING_CATEGORY(scxmlLog, "scxml.table")
+
+QEvent::Type ScxmlEvent::scxmlEventType = (QEvent::Type)QEvent::registerEventType();
+
+namespace {
+QByteArray objectId(QObject *obj, bool strict = false)
+{
+ Q_UNUSED(obj);
+ Q_UNUSED(strict);
+ Q_UNIMPLEMENTED();
+ return QByteArray();
+}
+
+} // anonymous namespace
+
+TableData::~TableData()
+{}
+
+QAtomicInt StateTablePrivate::m_sessionIdCounter = QAtomicInt(0);
+
+StateTablePrivate::StateTablePrivate()
+ : QStateMachinePrivate()
+ , m_sessionId(m_sessionIdCounter++)
+{
+}
+
+StateTablePrivate::~StateTablePrivate()
+{
+ delete m_executionEngine;
+ delete m_queuedEvents;
+}
+
+StateTable::StateTable(QObject *parent)
+ : QStateMachine(*new StateTablePrivate, parent)
+{
+ Q_D(StateTable);
+ d->m_executionEngine = new ExecutableContent::ExecutionEngine(this);
+ connect(this, &QStateMachine::finished, this, &StateTable::onFinished);
+}
+
+StateTable::StateTable(StateTablePrivate &dd, QObject *parent)
+ : QStateMachine(dd, parent)
+{
+ Q_D(StateTable);
+ d->m_executionEngine = new ExecutableContent::ExecutionEngine(this);
+ connect(this, &QStateMachine::finished, this, &StateTable::onFinished);
+}
+
+StateTablePrivate *StateTable::privateData()
+{
+ Q_D(StateTable);
+
+ return d;
+}
+
+int StateTable::sessionId() const
+{
+ Q_D(const StateTable);
+
+ return d->m_sessionId;
+}
+
+DataModel *StateTable::dataModel() const
+{
+ Q_D(const StateTable);
+
+ return d->m_dataModel;
+}
+
+void StateTable::setDataModel(DataModel *dataModel)
+{
+ Q_D(StateTable);
+
+ d->m_dataModel = dataModel;
+}
+
+void StateTable::setDataBinding(StateTable::BindingMethod b)
+{
+ Q_D(StateTable);
+
+ d->m_dataBinding = b;
+}
+
+StateTable::BindingMethod StateTable::dataBinding() const
+{
+ Q_D(const StateTable);
+
+ return d->m_dataBinding;
+}
+
+ExecutableContent::ExecutionEngine *StateTable::executionEngine() const
+{
+ Q_D(const StateTable);
+
+ return d->m_executionEngine;
+}
+
+TableData *StateTable::tableData() const
+{
+ Q_D(const StateTable);
+
+ return d->tableData;
+}
+
+void StateTable::setTableData(TableData *tableData)
+{
+ Q_D(StateTable);
+
+ d->tableData = tableData;
+}
+
+void StateTable::doLog(const QString &label, const QString &msg)
+{
+ qCDebug(scxmlLog) << label << ":" << msg;
+ emit log(label, msg);
+}
+
+void StateTable::beginSelectTransitions(QEvent *event)
+{
+ Q_D(StateTable);
+
+ if (event && event->type() != QEvent::None) {
+ switch (event->type()) {
+ case QEvent::StateMachineSignal: {
+ QStateMachine::SignalEvent* e = (QStateMachine::SignalEvent*)event;
+ QByteArray signalName = e->sender()->metaObject()->method(e->signalIndex()).methodSignature();
+ //signalName.replace(signalName.indexOf('('), 1, QLatin1Char('.'));
+ //signalName.chop(1);
+ //if (signalName.endsWith(QLatin1Char('.')))
+ // signalName.chop(1);
+ ScxmlEvent::EventType eventType = ScxmlEvent::External;
+ QObject *s = e->sender();
+ if (s == this) {
+ if (signalName.startsWith(QByteArray("event_"))){
+ d->_event.reset(signalName.mid(6), eventType, e->arguments());
+ break;
+ } else {
+ qCWarning(scxmlLog) << "Unexpected signal event sent to StateMachine "
+ << d->_name << ":" << signalName;
+ }
+ }
+ QByteArray senderName = QByteArray("@0");
+ if (s) {
+ senderName = objectId(s, true);
+ if (senderName.isEmpty() && !s->objectName().isEmpty())
+ senderName = s->objectName().toUtf8();
+ }
+ QList<QByteArray> namePieces;
+ namePieces << QByteArray("qsignal") << senderName << signalName;
+ QByteArray eventName = namePieces.join('.');
+ d->_event.reset(eventName, eventType, e->arguments());
+ } break;
+ case QEvent::StateMachineWrapped: {
+ QStateMachine::WrappedEvent * e = (QStateMachine::WrappedEvent *)event;
+ QObject *s = e->object();
+ QByteArray senderName = QByteArray("@0");
+ if (s) {
+ senderName = objectId(s, true);
+ if (senderName.isEmpty() && !s->objectName().isEmpty())
+ senderName = s->objectName().toUtf8();
+ }
+ QEvent::Type qeventType = e->event()->type();
+ QByteArray eventName;
+ QMetaObject metaObject = QEvent::staticMetaObject;
+ int maxIenum = metaObject.enumeratorCount();
+ for (int ienum = metaObject.enumeratorOffset(); ienum < maxIenum; ++ienum) {
+ QMetaEnum en = metaObject.enumerator(ienum);
+ if (QByteArray(en.name()) == QByteArray("Type")) {
+ eventName = QByteArray(en.valueToKey(qeventType));
+ break;
+ }
+ }
+ if (eventName.isEmpty())
+ eventName = QStringLiteral("E%1").arg((int)qeventType).toUtf8();
+ QList<QByteArray> namePieces;
+ namePieces << QByteArray("qevent") << senderName << eventName;
+ QByteArray name = namePieces.join('.');
+ ScxmlEvent::EventType eventType = ScxmlEvent::External;
+ // use e->spontaneous(); to choose internal/external?
+ d->_event.reset(name, eventType); // put something more in data for some elements like keyEvents and mouseEvents?
+ } break;
+ default:
+ if (event->type() == ScxmlEvent::scxmlEventType) {
+ d->_event = *static_cast<ScxmlEvent *>(event);
+ } else {
+ QEvent::Type qeventType = event->type();
+ QByteArray eventName = QStringLiteral("qdirectevent.E%1").arg((int)qeventType).toUtf8();
+ d->_event.reset(eventName);
+ qCWarning(scxmlLog) << "Unexpected event directly sent to StateMachine "
+ << d->_name << ":" << event->type();
+ }
+ break;
+ }
+ } else {
+ d->_event.clear();
+ }
+
+ dataModel()->setEvent(d->_event);
+}
+
+void StateTable::beginMicrostep(QEvent *event)
+{
+ Q_D(StateTable);
+
+ qCDebug(scxmlLog) << d->_name << " started microstep from state" << currentStates()
+ << "with event" << d->_event.name() << "from event type" << event->type();
+}
+
+void StateTable::endMicrostep(QEvent *event)
+{
+ Q_D(StateTable);
+ Q_UNUSED(event);
+
+ qCDebug(scxmlLog) << d->_name << " finished microstep in state (" << currentStates() << ")";
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
+
+void StateTablePrivate::noMicrostep()
+{
+ Q_Q(StateTable);
+
+ qCDebug(scxmlLog) << _name << " had no transition, stays in state (" << q->currentStates() << ")";
+}
+
+void StateTablePrivate::processedPendingEvents(bool didChange)
+{
+ Q_Q(StateTable);
+
+ qCDebug(scxmlLog) << _name << " finishedPendingEvents " << didChange << " in state ("
+ << q->currentStates() << ")";
+ emit q->reachedStableState(didChange);
+}
+
+void StateTablePrivate::beginMacrostep()
+{
+}
+
+void StateTablePrivate::endMacrostep(bool didChange)
+{
+ Q_Q(StateTable);
+ qCDebug(scxmlLog) << _name << " endMacrostep " << didChange << " in state ("
+ << q->currentStates() << ")";
+}
+
+void StateTablePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState)
+{
+ Q_Q(StateTable);
+
+ if (ScxmlFinalState *finalState = qobject_cast<ScxmlFinalState *>(guiltyState)) {
+ if (!q->isRunning())
+ return;
+ q->executionEngine()->execute(finalState->doneData(), forState->objectName());
+ }
+
+ QStateMachinePrivate::emitStateFinished(forState, guiltyState);
+}
+
+void StateTablePrivate::startupHook()
+{
+ Q_Q(StateTable);
+
+ q->submitQueuedEvents();
+}
+
+#endif // QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
+
+int StateTablePrivate::eventIdForDelayedEvent(const QByteArray &scxmlEventId)
+{
+ QMutexLocker locker(&delayedEventsMutex);
+
+ QHash<int, DelayedEvent>::const_iterator it;
+ for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) {
+ if (ScxmlEvent *e = dynamic_cast<ScxmlEvent *>(it->event)) {
+ if (e->sendid() == scxmlEventId) {
+ return it.key();
+ }
+ }
+ }
+
+ return -1;
+}
+
+QStringList StateTable::currentStates(bool compress)
+{
+ QSet<QAbstractState *> config = d_func()->configuration;
+ if (compress)
+ foreach (const QAbstractState *s, d_func()->configuration)
+ config.remove(s->parentState());
+ QStringList res;
+ foreach (const QAbstractState *s, config) {
+ QString id = s->objectName();
+ if (!id.isEmpty()) {
+ res.append(id);
+ }
+ }
+ std::sort(res.begin(), res.end());
+ return res;
+}
+
+static ScxmlState *findState(const QString &scxmlName, StateTable *parent)
+{
+ QList<QObject *> worklist;
+ worklist.reserve(parent->children().size() + parent->configuration().size());
+ worklist.append(parent);
+
+ while (!worklist.isEmpty()) {
+ QObject *obj = worklist.takeLast();
+ if (ScxmlState *state = qobject_cast<ScxmlState *>(obj)) {
+ if (state->objectName() == scxmlName)
+ return state;
+ }
+ worklist.append(obj->children());
+ }
+
+ return nullptr;
+}
+
+bool StateTable::hasState(const QString &scxmlStateName) const
+{
+ return findState(scxmlStateName, const_cast<StateTable *>(this)) != nullptr;
+}
+
+QMetaObject::Connection StateTable::connect(const QString &scxmlStateName, const char *signal,
+ const QObject *receiver, const char *method,
+ Qt::ConnectionType type)
+{
+ ScxmlState *state = findState(scxmlStateName, this);
+ return QObject::connect(state, signal, receiver, method, type);
+}
+
+void StateTable::setName(const QString &name)
+{
+ Q_D(StateTable);
+
+ d->_name = name;
+}
+
+void StateTable::setInitialSetup(ExecutableContent::ContainerId sequence)
+{
+ Q_D(StateTable);
+
+ d->m_initialSetup = sequence;
+}
+
+void StateTable::executeInitialSetup()
+{
+ Q_D(StateTable);
+
+ executionEngine()->execute(d->m_initialSetup);
+}
+
+static bool loopOnSubStates(QState *startState,
+ std::function<bool(QState *)> enteringState = nullptr,
+ std::function<void(QState *)> exitingState = nullptr,
+ std::function<void(QAbstractState *)> inAbstractState = nullptr)
+{
+ QList<int> pos;
+ QState *parentAtt = startState;
+ QObjectList childs = startState->children();
+ pos << 0;
+ while (!pos.isEmpty()) {
+ bool goingDeeper = false;
+ for (int i = pos.last(); i < childs.size() ; ++i) {
+ if (QAbstractState *as = qobject_cast<QAbstractState *>(childs.at(i))) {
+ if (QState *s = qobject_cast<QState *>(as)) {
+ if (enteringState && !enteringState(s))
+ continue;
+ pos.last() = i + 1;
+ parentAtt = s;
+ childs = s->children();
+ pos << 0;
+ goingDeeper = !childs.isEmpty();
+ break;
+ } else if (inAbstractState) {
+ inAbstractState(as);
+ }
+ }
+ }
+ if (!goingDeeper) {
+ do {
+ pos.removeLast();
+ if (pos.isEmpty())
+ break;
+ if (exitingState)
+ exitingState(parentAtt);
+ parentAtt = parentAtt->parentState();
+ childs = parentAtt->children();
+ } while (!pos.isEmpty() && pos.last() >= childs.size());
+ }
+ }
+ return true;
+}
+
+bool StateTable::init()
+{
+ dataModel()->setup();
+ executeInitialSetup();
+
+ bool res = true;
+ loopOnSubStates(this, std::function<bool(QState *)>(), [&res](QState *state) {
+ if (ScxmlState *s = qobject_cast<ScxmlState *>(state))
+ if (!s->init())
+ res = false;
+ if (ScxmlFinalState *s = qobject_cast<ScxmlFinalState *>(state))
+ if (!s->init())
+ res = false;
+ foreach (QAbstractTransition *t, state->transitions()) {
+ if (ScxmlTransition *scTransition = qobject_cast<ScxmlTransition *>(t))
+ if (!scTransition->init())
+ res = false;
+ }
+ });
+ foreach (QAbstractTransition *t, transitions()) {
+ if (ScxmlTransition *scTransition = qobject_cast<ScxmlTransition *>(t))
+ if (!scTransition->init())
+ res = false;
+ }
+ return res;
+}
+
+QString StateTable::name() const
+{
+ Q_D(const StateTable);
+
+ return d->_name;
+}
+
+void StateTable::submitError(const QByteArray &type, const QString &msg, const QByteArray &sendid)
+{
+ Q_D(StateTable);
+
+ qCDebug(scxmlLog) << "machine" << d->_name << "had error" << type << ":" << msg;
+ submitEvent(EventBuilder::errorEvent(type, sendid));
+}
+
+void StateTable::submitEvent1(const QString &event)
+{
+ submitEvent(event.toUtf8(), QVariantList());
+}
+
+void StateTable::submitEvent2(const QString &event, QVariant data)
+{
+ QVariantList dataValues;
+ if (data.isValid())
+ dataValues << data;
+ submitEvent(event.toUtf8(), dataValues);
+}
+
+void StateTable::submitEvent(ScxmlEvent *e)
+{
+ if (!e)
+ return;
+
+ EventPriority priority = e->eventType() == ScxmlEvent::External ? QStateMachine::NormalPriority
+ : QStateMachine::HighPriority;
+
+ if (isRunning())
+ postEvent(e, priority);
+ else
+ queueEvent(e, priority);
+}
+
+void StateTable::submitEvent(const QByteArray &event, const QVariantList &dataValues,
+ const QStringList &dataNames, ScxmlEvent::EventType type,
+ const QByteArray &sendid, const QString &origin,
+ const QString &origintype, const QByteArray &invokeid)
+{
+ Q_D(StateTable);
+
+ qCDebug(scxmlLog) << d->_name << ": submitting event" << event;
+
+ ScxmlEvent *e = new ScxmlEvent(event, type, dataValues, dataNames, sendid, origin, origintype, invokeid);
+ submitEvent(e);
+}
+
+void StateTable::submitDelayedEvent(int delayInMiliSecs, ScxmlEvent *e)
+{
+ Q_ASSERT(delayInMiliSecs > 0);
+ Q_D(StateTable);
+
+ if (!e)
+ return;
+
+ qCDebug(scxmlLog) << d->_name << ": submitting event" << e->name() << "with delay" << delayInMiliSecs << "ms" << "and sendid" << e->sendid();
+
+ Q_ASSERT(e->eventType() == ScxmlEvent::External);
+ int id = postDelayedEvent(e, delayInMiliSecs);
+
+ qCDebug(scxmlLog) << d->_name << ": delayed event" << e->name() << "(" << e << ") got id:" << id;
+}
+
+void StateTable::cancelDelayedEvent(const QByteArray &sendid)
+{
+ Q_D(StateTable);
+
+ int id = d->eventIdForDelayedEvent(sendid);
+
+ qCDebug(scxmlLog) << name() << ": canceling event" << sendid << "with id" << id;
+
+ if (id != -1)
+ QStateMachine::cancelDelayedEvent(id);
+}
+
+void StateTable::queueEvent(ScxmlEvent *event, EventPriority priority)
+{
+ Q_D(StateTable);
+
+ qCDebug(scxmlLog) << name() << ": queueing event" << event->name();
+
+ if (!d->m_queuedEvents)
+ d->m_queuedEvents = new QVector<StateTablePrivate::QueuedEvent>();
+ d->m_queuedEvents->append({event, priority});
+}
+
+void StateTable::submitQueuedEvents()
+{
+ Q_D(StateTable);
+
+ qCDebug(scxmlLog) << name() << ": submitting queued events";
+
+ if (d->m_queuedEvents) {
+ foreach (const StateTablePrivate::QueuedEvent &e, *d->m_queuedEvents)
+ postEvent(e.event, e.priority);
+ delete d->m_queuedEvents;
+ d->m_queuedEvents = nullptr;
+ }
+}
+
+bool StateTable::isLegalTarget(const QString &target) const
+{
+ return target.startsWith(QLatin1Char('#'));
+}
+
+bool StateTable::isDispatchableTarget(const QString &target) const
+{
+ return target == QStringLiteral("#_internal")
+ || target == QStringLiteral("#_scxml_%1").arg(sessionId());
+}
+
+void StateTable::onFinished()
+{
+ // The final state is also a stable state.
+ emit reachedStableState(true);
+}
+
+ScxmlEvent::ScxmlEvent(const QByteArray &name, ScxmlEvent::EventType eventType,
+ const QVariantList &dataValues, const QStringList &dataNames,
+ const QByteArray &sendid, const QString &origin,
+ const QString &origintype, const QByteArray &invokeid)
+ : QEvent(scxmlEventType), m_name(name), m_type(eventType), m_dataValues(dataValues), m_dataNames(dataNames)
+ , m_sendid(sendid), m_origin(origin), m_origintype(origintype), m_invokeid(invokeid)
+{ }
+
+QString ScxmlEvent::scxmlType() const {
+ switch (m_type) {
+ case Platform:
+ return QLatin1String("platform");
+ case Internal:
+ return QLatin1String("internal");
+ case External:
+ break;
+ }
+ return QLatin1String("external");
+}
+
+void ScxmlEvent::reset(const QByteArray &name, ScxmlEvent::EventType eventType, QVariantList dataValues,
+ const QByteArray &sendid, const QString &origin,
+ const QString &origintype, const QByteArray &invokeid) {
+ m_name = name;
+ m_type = eventType;
+ m_sendid = sendid;
+ m_origin = origin;
+ m_origintype = origintype;
+ m_invokeid = invokeid;
+ m_dataValues = dataValues;
+}
+
+void ScxmlEvent::clear() {
+ m_name = QByteArray();
+ m_type = External;
+ m_sendid = QByteArray();
+ m_origin = QString();
+ m_origintype = QString();
+ m_invokeid = QByteArray();
+ m_dataValues = QVariantList();
+}
+
+class ScxmlBaseTransition::Data
+{
+public:
+ QList<QByteArray> eventSelector;
+ QList<TransitionPtr> m_concreteTransitions;
+};
+
+ScxmlBaseTransition::ScxmlBaseTransition(QState *sourceState, const QList<QByteArray> &eventSelector)
+ : QAbstractTransition(sourceState)
+ , d(new Data)
+{
+ d->eventSelector = eventSelector;
+}
+
+ScxmlBaseTransition::ScxmlBaseTransition(QAbstractTransitionPrivate &dd, QState *parent,
+ const QList<QByteArray> &eventSelector)
+ : QAbstractTransition(dd, parent)
+ , d(new Data)
+{
+ d->eventSelector = eventSelector;
+}
+
+ScxmlBaseTransition::~ScxmlBaseTransition()
+{
+ delete d;
+}
+
+StateTable *ScxmlBaseTransition::table() const {
+ if (StateTable *t = qobject_cast<StateTable *>(parent()))
+ return t;
+ if (QState *s = sourceState())
+ return qobject_cast<StateTable *>(s->machine());
+ qCWarning(scxmlLog) << "could not resolve StateTable in " << transitionLocation();
+ return 0;
+}
+
+QString ScxmlBaseTransition::transitionLocation() const {
+ if (QState *state = sourceState()) {
+ QString stateName = state->objectName();
+ int transitionIndex = state->transitions().indexOf(const_cast<ScxmlBaseTransition *>(this));
+ return QStringLiteral("transition #%1 in state %2").arg(transitionIndex).arg(stateName);
+ }
+ return QStringLiteral("unbound transition @%1").arg((size_t)(void*)this);
+}
+
+bool ScxmlBaseTransition::eventTest(QEvent *event)
+{
+ if (d->eventSelector.isEmpty())
+ return true;
+ if (event->type() == QEvent::None)
+ return false;
+ StateTable *stateTable = table();
+ Q_ASSERT(stateTable);
+ QByteArray eventName = stateTable->privateData()->_event.name();
+ bool selected = false;
+ foreach (QByteArray eventStr, d->eventSelector) {
+ if (eventStr == "*") {
+ selected = true;
+ break;
+ }
+ if (eventStr.endsWith(".*"))
+ eventStr.chop(2);
+ if (eventName.startsWith(eventStr)) {
+ char nextC = '.';
+ if (eventName.size() > eventStr.size())
+ nextC = eventName.at(eventStr.size());
+ if (nextC == '.' || nextC == '(') {
+ selected = true;
+ if (event->type() != QEvent::StateMachineSignal && event->type() != ScxmlEvent::scxmlEventType) {
+ qCWarning(scxmlLog) << "unexpected triggering of event " << eventName
+ << " with type " << event->type() << " detected in "
+ << transitionLocation();
+ }
+ break;
+ }
+ }
+ }
+#ifdef SCXML_DEBUG
+ if (!d->m_concreteTransitions.isEmpty() && event->type() == QEvent::StateMachineSignal
+ && static_cast<QStateMachine::SignalEvent *>(event)->sender() != stateTable) {
+ bool selected2 = false;
+ foreach (TransitionPtr t, d->m_concreteTransitions) {
+ if (t->subEventTest(event))
+ selected2 = true;
+ }
+ if (selected != selected2) {
+ qCWarning(scxmlLog) << "text based triggering and signal based triggering differs for event"
+ << eventName << " text based comparison with '"
+ << d->eventSelector.join(' ')
+ << "' gives value " << selected
+ << " while the underlying concrete transitions give "
+ << selected2 << " in " << transitionLocation();
+ }
+ }
+#endif
+ return selected;
+}
+
+bool ScxmlBaseTransition::clear()
+{
+ foreach (TransitionPtr t, d->m_concreteTransitions)
+ sourceState()->removeTransition(t.data());
+ d->m_concreteTransitions.clear();
+ return true;
+}
+
+bool ScxmlBaseTransition::init()
+{
+ Q_ASSERT(d->m_concreteTransitions.isEmpty());
+ if (d->eventSelector.isEmpty())
+ return true;
+ bool failure = false;
+ foreach (const QByteArray &eventStr, d->eventSelector) {
+ QList<QByteArray> selector = eventStr.split('.');
+ if (selector.isEmpty())
+ continue;
+ else if (selector.first() == QByteArray("qsignal")) {
+ // FIXME: the sender cannot be found this way anymore. We need some tests before we enable/fix this code.
+ if (true) {
+ Q_UNIMPLEMENTED();
+ } else {
+ // FIXME starts here.
+// StateTable *stateTable = table();
+ if (selector.count() < 2) {
+ qCWarning(scxmlLog) << "qeventSelector requires a sender id in " << transitionLocation();
+ failure = true;
+ continue;
+ }
+ QObject *sender = nullptr; // stateTable->idToValue<QObject>(selector.value(1));
+ if (!sender) {
+ qCWarning(scxmlLog) << "could not find object with id " << selector.value(1)
+ << " used in " << transitionLocation();
+ failure = true;
+ continue;
+ }
+ QByteArray methodName = selector.value(2);
+ bool partial = !methodName.contains('(');
+ int minMethodLen = methodName.size();
+ const QMetaObject *metaObject = sender->metaObject();
+ int maxImethod = metaObject->methodCount();
+ for (int imethod = 0; imethod < maxImethod; ++imethod){
+ QMetaMethod m = metaObject->method(imethod);
+ if (m.methodType() != QMetaMethod::Signal) continue;
+ QByteArray mName = m.methodSignature();
+ if (methodName == mName // exact match
+ || ( // partial match, but excluding deleteLater() destroyed() that must be explicitly included
+ partial && mName.size() > minMethodLen && mName != QByteArray("deleteLater()")
+ && mName != QByteArray("destroyed()")
+ && (methodName.isEmpty() || (mName.startsWith(methodName)
+ && mName.at(methodName.size()) == '('))))
+ {
+ ConcreteSignalTransition *newT = new ConcreteSignalTransition(sender, mName.data(), sourceState());
+ newT->setTargetState(targetState()); // avoid?
+ d->m_concreteTransitions << TransitionPtr(newT);
+ }
+ }
+ if (d->m_concreteTransitions.isEmpty()) {
+ QList<QByteArray> knownSignals;
+ for (int imethod = 0; imethod < maxImethod; ++imethod){
+ QMetaMethod m = metaObject->method(imethod);
+ if (m.methodType() != QMetaMethod::Signal) continue;
+ QByteArray mName = m.methodSignature();
+ knownSignals.append(mName);
+ }
+ qCWarning(scxmlLog) << "eventSelector failed to match anything in "
+ << transitionLocation() << ", selector is: "
+ << d->eventSelector.join(' ') << " and known signals are:\n "
+ << knownSignals.join(' ');
+ failure = true; // ignore instead??
+ }
+ } // end of FIXME
+ } else if (selector.first() == QByteArray("qevent")){
+ qCWarning(scxmlLog) << "selector of qevent type to implement";
+ failure = true;
+ } else {
+ // this is expected to be a custom scxml event, no binding required
+ }
+ }
+ return !failure;
+}
+
+void ScxmlBaseTransition::onTransition(QEvent *event)
+{
+ Q_UNUSED(event);
+}
+
+/////////////
+
+static QList<QByteArray> filterEmpty(const QList<QByteArray> &events) {
+ QList<QByteArray> res;
+ int oldI = 0;
+ for (int i = 0; i < events.size(); ++i) {
+ if (events.at(i).isEmpty()) {
+ res.append(events.mid(oldI, i - oldI));
+ oldI = i + 1;
+ }
+ }
+ if (oldI > 0) {
+ res.append(events.mid(oldI));
+ return res;
+ }
+ return events;
+}
+
+class ScxmlTransition::Data
+{
+public:
+ EvaluatorId conditionalExp = NoEvaluator;
+ ScxmlEvent::EventType type;
+ ExecutableContent::ContainerId instructionsOnTransition = ExecutableContent::NoInstruction;
+};
+
+ScxmlTransition::ScxmlTransition(QState *sourceState, const QList<QByteArray> &eventSelector)
+ : ScxmlBaseTransition(sourceState, filterEmpty(eventSelector))
+ , d(new Data)
+{
+ Q_ASSERT(sourceState);
+ d->type = ScxmlEvent::External;
+}
+
+ScxmlTransition::~ScxmlTransition()
+{
+ delete d;
+}
+
+bool ScxmlTransition::eventTest(QEvent *event)
+{
+// if (table()->engine()) qCDebug(scxmlLog) << qPrintable(table()->engine()->evaluate(QLatin1String("JSON.stringify(_event)")).toString());
+ if (ScxmlBaseTransition::eventTest(event)) {
+ bool ok = true;
+ if (d->conditionalExp != NoEvaluator)
+ return table()->dataModel()->evaluateToBool(d->conditionalExp, &ok) && ok;
+ return true;
+ }
+
+ return false;
+}
+
+void ScxmlTransition::onTransition(QEvent *)
+{
+ table()->executionEngine()->execute(d->instructionsOnTransition);
+}
+
+StateTable *ScxmlTransition::table() const {
+ // work around a bug in QStateMachine
+ if (StateTable *t = qobject_cast<StateTable *>(sourceState()))
+ return t;
+ return qobject_cast<StateTable *>(machine());
+}
+
+void ScxmlTransition::setInstructionsOnTransition(ExecutableContent::ContainerId instructions)
+{
+ d->instructionsOnTransition = instructions;
+}
+
+void ScxmlTransition::setConditionalExpression(EvaluatorId evaluator)
+{
+ d->conditionalExp = evaluator;
+}
+
+class ScxmlState::Data
+{
+public:
+ ExecutableContent::ContainerId initInstructions = ExecutableContent::NoInstruction;
+ ExecutableContent::ContainerId onEntryInstructions = ExecutableContent::NoInstruction;
+ ExecutableContent::ContainerId onExitInstructions = ExecutableContent::NoInstruction;
+};
+
+ScxmlState::ScxmlState(QState *parent)
+ : QState(parent)
+ , d(new Data)
+{}
+
+ScxmlState::~ScxmlState()
+{
+ delete d;
+}
+
+StateTable *ScxmlState::table() const {
+ return qobject_cast<StateTable *>(machine());
+}
+
+bool ScxmlState::init()
+{
+ return true;
+}
+
+QString ScxmlState::stateLocation() const
+{
+ return QStringLiteral("State %1").arg(objectName());
+}
+
+void ScxmlState::setInitInstructions(ExecutableContent::ContainerId instructions)
+{
+ d->initInstructions = instructions;
+}
+
+void ScxmlState::setOnEntryInstructions(ExecutableContent::ContainerId instructions)
+{
+ d->onEntryInstructions = instructions;
+}
+
+void ScxmlState::setOnExitInstructions(ExecutableContent::ContainerId instructions)
+{
+ d->onExitInstructions = instructions;
+}
+
+ScxmlState::ScxmlState(QStatePrivate &dd, QState *parent)
+ : QState(dd, parent)
+{}
+
+void ScxmlState::onEntry(QEvent *event)
+{
+ if (d->initInstructions != ExecutableContent::NoInstruction) {
+ table()->executionEngine()->execute(d->initInstructions);
+ d->initInstructions = ExecutableContent::NoInstruction;
+ }
+ QState::onEntry(event);
+ table()->executionEngine()->execute(d->onEntryInstructions);
+ emit didEnter();
+}
+
+void ScxmlState::onExit(QEvent *event)
+{
+ emit willExit();
+ QState::onExit(event);
+ table()->executionEngine()->execute(d->onExitInstructions);
+}
+
+class ScxmlFinalState::Data
+{
+public:
+ ExecutableContent::ContainerId doneData = ExecutableContent::NoInstruction;
+ ExecutableContent::ContainerId onEntryInstructions = ExecutableContent::NoInstruction;
+ ExecutableContent::ContainerId onExitInstructions = ExecutableContent::NoInstruction;
+};
+
+ScxmlFinalState::ScxmlFinalState(QState *parent)
+ : QFinalState(parent)
+ , d(new Data)
+{}
+
+ScxmlFinalState::~ScxmlFinalState()
+{
+ delete d;
+}
+
+StateTable *ScxmlFinalState::table() const {
+ return qobject_cast<StateTable *>(machine());
+}
+
+bool ScxmlFinalState::init()
+{
+ return true;
+}
+
+Scxml::ExecutableContent::ContainerId ScxmlFinalState::doneData() const
+{
+ return d->doneData;
+}
+
+void ScxmlFinalState::setDoneData(Scxml::ExecutableContent::ContainerId doneData)
+{
+ d->doneData = doneData;
+}
+
+void ScxmlFinalState::setOnEntryInstructions(ExecutableContent::ContainerId instructions)
+{
+ d->onEntryInstructions = instructions;
+}
+
+void ScxmlFinalState::setOnExitInstructions(ExecutableContent::ContainerId instructions)
+{
+ d->onExitInstructions = instructions;
+}
+
+void ScxmlFinalState::onEntry(QEvent *event)
+{
+ QFinalState::onEntry(event);
+ table()->executionEngine()->execute(d->onEntryInstructions);
+}
+
+void ScxmlFinalState::onExit(QEvent *event)
+{
+ QFinalState::onExit(event);
+ table()->executionEngine()->execute(d->onExitInstructions);
+}
+
+} // namespace Scxml