From f97723ad6103294d7802d58f7ac417949738fa94 Mon Sep 17 00:00:00 2001 From: No'am Rosenthal Date: Fri, 27 Nov 2009 12:45:45 -0800 Subject: QML binding to SCXML --- examples/qml/blackjack/blackjack.qml | 213 ++++++++++++++----- examples/qml/blackjack/blackjack.scxml | 372 +++++++++++++++------------------ examples/qml/blackjack/main.cpp | 1 + qmlscxml/qmlscxml.cpp | 45 +++- src/qscxml.cpp | 180 +++++++++------- 5 files changed, 474 insertions(+), 337 deletions(-) diff --git a/examples/qml/blackjack/blackjack.qml b/examples/qml/blackjack/blackjack.qml index 976661a..6e68022 100644 --- a/examples/qml/blackjack/blackjack.qml +++ b/examples/qml/blackjack/blackjack.qml @@ -9,85 +9,196 @@ Rectangle { id: controller source: "blackjack.scxml" } + states: [ + State { + name: "placeBets" + when: controller.current.waitForBet + PropertyChanges { + target: betContainer + opacity: 1 + } + PropertyChanges { + target: welcomeText + text: "Please place your bet" + } + }, + State { + name: "waitForAction" + when: controller.current.waitForAction + PropertyChanges { + target: welcomeText + text: "Hit or stand?" + } + PropertyChanges { + target: actionContainer + opacity: 1 + } + }, + State { + name: "win" + when: controller.current.win + PropertyChanges { + target: welcomeText + text: "You win!" + } + }, + State { + name: "lose" + when: controller.current.loss + PropertyChanges { + target: welcomeText + text: "You lose..." + } + }, + State { + name: "draw" + when: controller.current.draw + PropertyChanges { + target: welcomeText + text: "It's a draw." + } + }, + State { + name: "game" + when: controller.current.game + PropertyChanges { + target: welcomeText + text: "Welcome" + } + }, + State { + name: "default" + when: !controller.current.waitForBet + PropertyChanges { + target: betContainer + opacity: 0 + } + } + ] Text { id: welcomeText text: "Blackjack" font.pixelSize: 24 - states: [ - State { - name: "placeBets" - when: controller.current.waitForBet - PropertyChanges { - target: welcomeText - text: "Please place your bet" - } - }, - State { - name: "game" - when: controller.current.game - PropertyChanges { - target: welcomeText - text: "Welcome" - } - } - ] + height: 40 } Rectangle { color: "white" - x: 100 - y: 100 + height: 40 + id: pointsContainer + anchors.top: welcomeText.bottom Text { color: "blue" text: "You have " + controller.data.points + " points" + font.pixelSize: 24 } } + Text { + id: handContainer + height: 30 + font.pixelSize: 24 + anchors.top: pointsContainer.bottom + color: "red" + text: "You: "+controller.data.myHand + } + Text { + id: dealerContainer + height: 30 + font.pixelSize: 24 + anchors.left: handContainer.right + anchors.top: handContainer.top + anchors.leftMargin: 10 + text: "Dealer: "+controller.data.dealerHand + } Rectangle { color: "white" - id: betEditBg - x: 80 - y: 400 + id: betContainer + anchors.top: handContainer.bottom + radius: 5 width: 100 - height: 20 + height: 40 + color: "#ffcc33" + opacity: Behavior { NumberAnimation { duration: 300; easing: "InOutQuad" } } TextInput { id: betEdit color: "black" - anchors.fill: parent text: controller.data.pointsToBet + font.pixelSize: 24 + anchors.fill: parent + horizontalAlignment: "AlignHCenter" } - states: [ - State { - name: "placeBets" - when: controller.current.waitForBet - PropertyChanges { - target: betEditBg - color: "#ffcc33" - opacity: 1 - } - }, - State { - name: "default" - when: !controller.current.waitForBet - PropertyChanges { - target: betEditBg - opacity: 0 + Rectangle { + width: 100 + height: betEdit.height + radius: 5 + color: "blue" + anchors.left: betEdit.right + MouseRegion { + anchors.fill: parent + onClicked: { + controller.data.pointsToBet = betEdit.text; + controller.events.bet.raise(); } } - ] + Text { + color: "#ffffcc" + text: "Bet" + font.weight: "Bold" + font.pixelSize: 18 + anchors.fill: parent + horizontalAlignment: "AlignHCenter" + verticalAlignment: "AlignVCenter" + } + } } Rectangle { - anchors.left: betEditBg.right - anchors.top: betEditBg.top - anchors.bottom: betEditBg.bottom + color: "white" + id: actionContainer + anchors.top: handContainer.bottom + radius: 5 width: 100 - color: "blue" - MouseRegion { + height: 40 + color: "#ffcc33" + opacity: 0 + Rectangle { + id: hitButton + color: "green" anchors.fill: parent - onClicked: { controller.events.bet.raise(); } + opacity: Behavior { NumberAnimation { duration: 300; easing: "InOutQuad" } } + MouseRegion { + anchors.fill: parent + onClicked: { + controller.events.hit.raise(); + } + } + Text { + color: "#ffffcc" + text: "Hit" + anchors.fill: parent + horizontalAlignment: "AlignHCenter" + verticalAlignment: "AlignVCenter" + } } - Text { - color: "yellow" - text: "Bet" + Rectangle { + id: standButton + width: 100 + height: hitButton.height + radius: 5 + color: "#9966cc" + anchors.left: hitButton.right + MouseRegion { + anchors.fill: parent + onClicked: { + controller.events.stand.raise(); + } + } + Text { + color: "#ffffcc" + text: "Stand" + anchors.fill: parent + horizontalAlignment: "AlignHCenter" + verticalAlignment: "AlignVCenter" + } } } diff --git a/examples/qml/blackjack/blackjack.scxml b/examples/qml/blackjack/blackjack.scxml index ab1b9cd..0c32a3e 100644 --- a/examples/qml/blackjack/blackjack.scxml +++ b/examples/qml/blackjack/blackjack.scxml @@ -1,224 +1,196 @@ - - + + - + + + + - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + - - - - - - [[welcomeLabel,"text","Game Over"]] - - - - - - - [[newRoundButton,"enabled",true]] - - - - - + + + + + + + - + + + + + + + + + + - + - - - [[welcomeLabel,"text","You Lost..."]] - + + + + + + + + + + + + + + + - [[welcomeLabel,"text","You It's a draw."]] - - - + + + - - + - - - diff --git a/examples/qml/blackjack/main.cpp b/examples/qml/blackjack/main.cpp index dd7777a..87639d6 100644 --- a/examples/qml/blackjack/main.cpp +++ b/examples/qml/blackjack/main.cpp @@ -48,6 +48,7 @@ int main(int argc, char ** argv) { QApplication app(argc, argv); + qsrand(QDateTime::currentDateTime().toTime_t()); QmlView view(NULL); QmlComponent component(view.engine(), "blackjack.qml"); diff --git a/qmlscxml/qmlscxml.cpp b/qmlscxml/qmlscxml.cpp index 04a4c89..367fff1 100644 --- a/qmlscxml/qmlscxml.cpp +++ b/qmlscxml/qmlscxml.cpp @@ -41,6 +41,7 @@ #include "qmlscxml.h" #include "qscxml.h" #include +#include #include "qmlpropertymap.h" class QmlScxmlEventProxy : public QObject @@ -61,7 +62,9 @@ class QmlScxmlEventProxy : public QObject public Q_SLOTS: void raise() { - scxml->postNamedEvent(eventName); + if (scxml) { + scxml->postDelayedEvent(new QScxmlEvent(eventName),0); + } } void onEvent(const QString & e) @@ -122,7 +125,6 @@ namespace { }; void QmlScxml::setSource(const QUrl & u) { - qDebug() << u; if (u != m_source) { m_source = u; if (scxml) { @@ -135,6 +137,7 @@ void QmlScxml::setSource(const QUrl & u) connect(scxml,SIGNAL(stopped()),this,SIGNAL(runningChanged())); connect(scxml,SIGNAL(finished()),this,SIGNAL(finished())); connect(scxml,SIGNAL(eventTriggered(QString)),this,SIGNAL(trigger(QString))); + connect(scxml,SIGNAL(dataChanged(QString,QVariant)),this,SLOT(onDataChanged(QString,QVariant))); connect(scxml,SIGNAL(configurationChanged()),this,SLOT(checkConfig())); if (m_states) delete m_states; @@ -185,18 +188,42 @@ void QmlScxml::setSource(const QUrl & u) } } +class QmlScxmlDataTestTimer : public QTimer +{ + QString key; + QScxml* scxml; + QmlPropertyMap* propMap; + public: + QmlScxmlDataTestTimer(const QString & k, QScxml* qs, QmlPropertyMap* pm) + : QTimer(qs),key(k),scxml(qs),propMap(pm) + { + setInterval(0); + setSingleShot(true); + start(); + } + + void timerEvent(QTimerEvent* te) + { + if (te->timerId() == timerId()) { + + scxml->setData(key,propMap->value(key)); + deleteLater(); + } + } + +}; + void QmlScxml::onDataChanged(const QString & key) { - qDebug() << "onDataChanged" << key; - m_data->blockSignals(true); - scxml->setData(key,(*m_data)[key]); - m_data->blockSignals(false); + new QmlScxmlDataTestTimer(key,scxml,m_data); } void QmlScxml::onDataChanged(const QString & key, const QVariant & value) { - qDebug() << "onDataChanged" << key << value; if (m_data) { - (*m_data)[key] = value; + if ((*m_data)[key] != value) { + (*m_data)[key] = value; + emit dataChanged(m_data); + } } } @@ -243,7 +270,7 @@ void QmlScxml::setRunning(bool r) void QmlScxml::raise(const QString & e) { if (scxml) { - scxml->postNamedEvent(e); + scxml->postDelayedEvent(new QScxmlEvent(e),0); } } void QmlScxml::stop() diff --git a/src/qscxml.cpp b/src/qscxml.cpp index 3896bda..68412ed 100644 --- a/src/qscxml.cpp +++ b/src/qscxml.cpp @@ -70,6 +70,7 @@ #include #include #include +#include @@ -199,6 +200,8 @@ class QScxmlPrivate QSet knownEvents; + QScriptClass* scriptClass; + static QHash sessions; }; QHash QScxmlPrivate::sessions; @@ -439,19 +442,41 @@ static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine) return QScriptValue(); } +static QScriptValue notifyChange(QScriptContext *context, QScriptEngine * engine) +{ + if (context->argumentCount() > 0) { + QScriptValue val = context->argument(0); + if (val.data().isObject()) { + QString key = val.data().property("key").toString(); + if (key != "") { + notifyDataChange(engine,key,val.data().property("value")); + } + } + } + return QScriptValue(); +} +static void notifyDataChange (QScriptEngine* engine, const QString & id, const QScriptValue & v) +{ + QScxml* scxml = qobject_cast(engine->globalObject().property("scxml").toQObject()); + if (scxml) { + scxml->dataChanged(id,v.toVariant()); + } +} + + static QScriptValue dataAccess(QScriptContext *context, QScriptEngine * engine) { if (context->argumentCount() == 0) { // getter - return context->callee().property("value"); + return context->callee().data().property("value"); } else if (context->argumentCount() == 1) { // setter QScriptValue val = context->argument(0); - context->callee().setProperty("value",val); - qDebug() << "data set" << val.toVariant() << context->callee().property("key").toString(); + context->callee().data().setProperty("value",val); + val.setData(context->callee().data()); QScxml* scxml = qobject_cast(engine->globalObject().property("scxml").toQObject()); if (scxml) { - scxml->dataChanged(context->callee().property("key").toString(),val.toVariant()); + scxml->dataChanged(context->callee().data().property("key").toString(),val.toVariant()); } return val; } else @@ -494,9 +519,9 @@ void QScxmlPrivate::initScriptEngine(QScxml* thiz) scxmlObj.setProperty("setTimeout",scriptEng->newFunction(QScxmlFunctions::setTimeout)); scxmlObj.setProperty("clearTimeout",scriptEng->newFunction(QScxmlFunctions::clearTimeout)); scxmlObj.setProperty("connectSignalToEvent",scriptEng->newFunction(QScxmlFunctions::connectSignalToEvent)); + scxmlObj.setProperty("notifyChange",scriptEng->newFunction(QScxmlFunctions::notifyChange)); QScriptValue dmObj = scriptEng->newObject(); dataObj = scriptEng->newObject(); - dataObj.setProperty("_values",scriptEng->newObject()); glob.setProperty("_data",dataObj); glob.setProperty("_global",scriptEng->globalObject()); glob.setProperty("scxml",scxmlObj); @@ -595,9 +620,9 @@ QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine) { } -void QScxmlTransition::setConditionExpression(const QString & c) +void QScxmlTransition::setConditionExpression(const QString & cond) { - prog = QScriptProgram(c,scxml->baseUrl().toLocalFile()); + prog = QScriptProgram(cond,scxml->baseUrl().toLocalFile()); } QString QScxmlTransition::conditionExpression() const @@ -936,10 +961,7 @@ void QScxml::postNamedEvent(const QString & event) void QScxml::setData(const QString & id, const QVariant & val) { - QScriptValue accessor = pvt->dataObj.property(id); - if (accessor.isFunction()) { - accessor.setProperty("value",scriptEngine()->newVariant(val)); - } + pvt->dataObj.setProperty(id,scriptEngine()->newVariant(val)); } /*! @@ -1100,78 +1122,80 @@ 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"; +namespace { + const char SCXML_NAMESPACE [] = "http://www.w3.org/2005/07/scxml"; -struct ScTransitionInfo -{ - - QScxmlTransition* transition; - QStringList targets; - QString script; - ScTransitionInfo() : transition(NULL) {} -}; + struct ScTransitionInfo + { -class QScxmlScriptExec : public QObject -{ - Q_OBJECT - QScriptProgram prog; - QScxml* scxml; - public: - QScxmlScriptExec(const QString & scr, QScxml* scx) : - prog(QScriptProgram(scr,scx->baseUrl().toLocalFile())),scxml(scx) - { - } - public Q_SLOTS: - void exec() - { - scxml->executeScript(prog); - } -}; + QScxmlTransition* transition; + QStringList targets; + QString script; + ScTransitionInfo() : transition(NULL) {} + }; -struct ScStateInfo -{ - QString initial; -}; + class QScxmlScriptExec : public QObject + { + Q_OBJECT + QScriptProgram prog; + QScxml* scxml; + public: + QScxmlScriptExec(const QString & scr, QScxml* scx) : + prog(QScriptProgram(scr,scx->baseUrl().toLocalFile())),scxml(scx) + { + } + public Q_SLOTS: + void exec() + { + scxml->executeScript(prog); + } + }; -struct ScHistoryInfo -{ - QHistoryState* hstate; - QString defaultStateID; -}; + struct ScStateInfo + { + QString initial; + }; -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) + struct ScHistoryInfo { - } + QHistoryState* hstate; + QString defaultStateID; + }; - void applyScript() + struct ScExecContext { - 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(triggered()),exec,SLOT(exec())); - break; - default: - delete exec; - break; + 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(triggered()),exec,SLOT(exec())); + break; + default: + delete exec; + break; + } } } - } + }; }; class QScxmlLoader @@ -1436,21 +1460,24 @@ void QScxmlLoader::loadState ( curTransition->setObjectName(QString ("%1 to %2 on %3 if %4").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefixes().join(" ")).arg(curTransition->conditionExpression())); } } else if (r.name().toString().compare("data",Qt::CaseInsensitive) == 0) { - QScriptValue val = qScriptValueFromValue(stateMachine->scriptEngine(),"") ; + QScriptEngine* engine = stateMachine->scriptEngine(); + QScriptValue val = qScriptValueFromValue(engine,"") ; 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()); + val = engine->evaluate(r.attributes().value("expr").toString()); } else { QString t = r.readElementText(); if (!t.isEmpty()) - val = stateMachine->scriptEngine()->evaluate(t); + val = engine->evaluate(t); } } - QScriptValue func = stateMachine->scriptEngine()->newFunction(QScxmlFunctions::dataAccess); - func.setProperty("key",id); + QScriptValue func = engine->newFunction(QScxmlFunctions::dataAccess); + QScriptValue accessor = engine->newObject(); + accessor.setProperty("key",id); + func.setData(accessor); stateMachine->pvt->dataObj.setProperty(id,func,QScriptValue::PropertyGetter|QScriptValue::PropertySetter); stateMachine->pvt->dataObj.setProperty(id,val); } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) { @@ -1532,7 +1559,6 @@ void QScxmlLoader::loadState ( } } } - QMap QScxml::data() const { QMap d; -- cgit v1.2.3