diff options
author | Noam Rosenthal <nrosenth@nokia.com> | 2009-06-08 12:27:03 -0700 |
---|---|---|
committer | Noam Rosenthal <nrosenth@nokia.com> | 2009-06-08 12:27:03 -0700 |
commit | d0441f605434a89b53735427e4e81182c65debbd (patch) | |
tree | b96d25dc89cdb523c007a22bc0deed3a5aa5dd56 | |
parent | a6553f68f17c28adca049857686496a69b4c1e7a (diff) |
scxml for 4.6
34 files changed, 2491 insertions, 62 deletions
diff --git a/examples/blackjack/bj.qrc b/examples/blackjack/bj.qrc new file mode 100644 index 0000000..6e39934 --- /dev/null +++ b/examples/blackjack/bj.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/" > + <file>blackjack.scxml</file> + </qresource> +</RCC> diff --git a/examples/blackjack/blackjack.pro b/examples/blackjack/blackjack.pro new file mode 100644 index 0000000..29c7351 --- /dev/null +++ b/examples/blackjack/blackjack.pro @@ -0,0 +1,14 @@ +# ------------------------------------------------- +# Project created by QtCreator 2008-12-16T16:32:05 +# ------------------------------------------------- +QT += script +DEPENDPATH += . +INCLUDEPATH += . +TARGET = blackjack +CONFIG += console +CONFIG -= app_bundle +TEMPLATE = app +SOURCES += main.cpp +include($$PWD/../../src/qtstatemachine.pri) +FORMS += blackjack.ui +RESOURCES += bj.qrc diff --git a/examples/blackjack/blackjack.scxml b/examples/blackjack/blackjack.scxml new file mode 100644 index 0000000..6c98eb5 --- /dev/null +++ b/examples/blackjack/blackjack.scxml @@ -0,0 +1,305 @@ +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + initial="root" profile="ecmascript"> + <script><![CDATA[ + var Suits = "CDHS"; + var Ranks = "-A23456789TJQK"; + + function CardToString() + { + return "" + Ranks[this.rank] + Suits[this.suit]; + } + + function Card (r,s) + { + this.rank = r; + this.suit = s; + this.minValue = Math.min(r,10); + this.toString = CardToString; + } + + function updateDisplay () + { + cardsLabel.text = "My Cards: " + myDeck + " Dealer Cards: " + dealerCards; + } + + function randomSort (a,b) + { + return Math.random() * 3 - 1; + } + + function deckReset() + { + this.clear (); + for (var i=1; i <= 13; ++i) + for (var j = 0; j < 4; ++j) + this.cards.push(new Card(i,j)); + this.cards.sort(randomSort); + } + + function deckDraw () + { + return this.cards.pop(); + } + + function deckDrawFrom (d) + { + var c = d.draw (); + this.cards.push(c); + updateDisplay (); + } + + function deckClear () + { + this.cards = new Array; + } + + function deckEvalMin () + { + var minVal = 0; + var cardCount = this.cards.length; + for (c in this.cards) { + minVal += this.cards[c].minValue; + } + if (cardCount > 4 && minVal < 22) + minVal = 21; + return minVal; + } + + function deckEvalBest () + { + var bestVal = this.evalMin(); + if (bestVal > 21) + return 0; + else if (bestVal == 21) + return bestVal; + + for (i in this.cards) { + if (this.cards[i].rank == 1) + { + var v = bestVal + 10; + if (v <= 21) + bestVal = v; + } + } + return bestVal; + + } + + function deckToString () + { + var s = ""; + for (i in this.cards) + s += this.cards[i].toString() + ":"; + + return s; + } + + function Deck() + { + this.draw = deckDraw; + this.cards = new Array(); + this.reset = deckReset; + this.clear = deckClear; + this.evalMin = deckEvalMin; + this.evalBest = deckEvalBest; + this.toString = deckToString; + this.drawFrom = deckDrawFrom; + } + + + function hitMe () + { + myDeck.drawFrom (availDeck); + } + ]]></script> + <final id="exit" /> + <state id="root" initial="newgame"> + <onentry> + <script> + var myDeck = new Deck; + var dealerCards = new Deck; + var availDeck = new Deck; + var pot = 0; + var points = 1000; + + </script> + + </onentry> + <invoke type="q-bindings"> + <content> + [[welcomeLabel,"text","Welcome to Blackjack"]] + </content> + </invoke> + <transition event="q-signal:newGameButton.clicked()" target="newgame" /> + <state id="newgame"> + <onentry> + <script> + points = 1000; + pointsLabel.text = points; + </script> + </onentry> + <transition target="newround" /> + </state> + <state id="quitdlg"> + <invoke type="q-messagebox"> + <content> + { + "parent" : gameWidget, + "icon" : QMessageBox.Question, + "windowTitle" : "Exit Blackjack", + "text" : "Are you sure?", + "standardButtons" : + QMessageBox.Yes|QMessageBox.No + } + </content> + </invoke> + <transition event="q-messagebox.finished" target="exit" cond="_event.data[0]==QMessageBox.Yes" /> + <transition event="q-messagebox.finished" target="gamestate" cond="_event.data[0]==QMessageBox.No" /> + </state> + <state id="game"> + <history type="deep" id="gamestate" /> + <transition event="q-signal:exitButton.clicked()" target="quitdlg" /> + <state id="newround"> + <onentry> + <script> + pot = 0; + availDeck.reset (); + myDeck.clear (); + dealerCards.clear(); + dealerCards.drawFrom(availDeck); + hitMe (); + hitMe (); + </script> + </onentry> + <transition target="waitForBet" /> + </state> + <state id="waitForBet"> + <invoke type="q-bindings"><content> + [ + [betEdit,"enabled",true], + [betButton,"enabled",true], + [surrenderButton,"enabled",true], + [welcomeLabel,"text","Please place your bet"] + ] + </content></invoke> + <transition event="q-signal:betButton.clicked()" target="testCards" cond="parseInt(betEdit.text) <= points"> + <script> + pot = betEdit.text; + points -= pot; + pointsLabel.text = points; + </script> + </transition> + <transition event="q-signal:betButton.clicked()" target="betTooHigh" cond="parseInt(betEdit.text) > points"> + </transition> + <transition event="q-signal:surrenderButton.clicked()" target="newround" /> + </state> + <state id="betTooHigh"> + <invoke type="q-messagebox"> + <content> + { + "parent" : betEdit, + "icon" : QMessageBox.Warning, + "windowTitle" : "Bet is Too High", + "text" : "Please Place Another Bet", + "standardButtons" : + QMessageBox.Ok + } + </content> + </invoke> + <transition event="q-messagebox.finished" target="waitForBet" /> + <transition event="bth-mb-timeout" target="waitForBet" /> + <onentry> + <send event="'bth-mb-timeout'" delay="'1500ms'" /> + </onentry> + </state> + <state id="testCards"> + <transition target="loss" cond="myDeck.evalBest() == 0" /> + <transition target="getDealerCards" cond="myDeck.evalBest() == 21" /> + <transition target="waitForAction" cond="myDeck.evalBest() %21 != 0" /> + </state> + + <state id="waitForAction"> + <transition event="q-signal:hitButton.clicked()" target="testCards"> + <script>hitMe (); </script> + </transition> + <transition event="q-signal:standButton.clicked()" target="getDealerCards" /> + <invoke type="q-bindings"><content> + [ + [welcomeLabel,"text","Hit/Stand?"], + [hitButton,"enabled",true], + [standButton,"enabled",true] + ] + </content></invoke> + </state> + + <state id="getDealerCards"> + <onentry> + <script><![CDATA[ + while (dealerCards.evalBest() > 0 && dealerCards.evalBest() < 17) + { + dealerCards.drawFrom(availDeck); + } + ]]></script> + <raise event="doneWithCards" /> + </onentry> + <transition target="checkWinner" /> + </state> + + <state id="checkWinner"> + <onentry> + <script> + _global.diff = myDeck.evalBest() - dealerCards.evalBest(); + </script> + </onentry> + + <transition cond="diff>0" target="win" /> + <transition cond="diff<0" target="loss" /> + <transition cond="diff==0" target="draw" /> + </state> + <state id="endGame"> + <invoke type="q-bindings"><content>[[welcomeLabel,"text","Game Over"]]</content></invoke> + <transition event="timeout" target="newgame" /> + <onentry> + <send event="'timeout'" delay="'3s'" /> + </onentry> + </state> + <state id="endRound"> + <invoke type="q-bindings"><content>[[newRoundButton,"enabled",true]]</content></invoke> + <transition event="q-signal:newRoundButton.clicked()" target="newround" /> + <transition event="timeout" target="newround" /> + <onentry> + <send event="'timeout'" delay="'3s'" /> + </onentry> + + <state id="win"> + <onentry> + <script> + points = parseInt(points) + parseInt(pot) * 2; + pointsLabel.text = points; + </script> + </onentry> + <invoke type="q-bindings"><content>[[welcomeLabel,"text","You Won!"]]</content></invoke> + </state> + <state id="loss"> + <invoke type="q-bindings"><content>[[welcomeLabel,"text","You Lost..."]]</content></invoke> + <transition cond="points == 0" target="endGame" /> + </state> + <state id="draw"> + <invoke type="q-bindings"><content>[[welcomeLabel,"text","You It's a draw."]]</content></invoke> + <onentry> + <script> + points = parseInt(points) + parseInt(pot); + pointsLabel.text = points; + </script> + </onentry> + </state> + </state> + </state> + + </state> + +</scxml> + + + diff --git a/examples/blackjack/blackjack.ui b/examples/blackjack/blackjack.ui new file mode 100644 index 0000000..187ec62 --- /dev/null +++ b/examples/blackjack/blackjack.ui @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>gameWidget</class> + <widget class="QWidget" name="gameWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>520</width> + <height>173</height> + </rect> + </property> + <property name="windowTitle"> + <string>SCXML Blackjack Example</string> + </property> + <property name="styleSheet"> + <string/> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QLabel" name="welcomeLabel"> + <property name="font"> + <font> + <pointsize>18</pointsize> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="cardsLabel"> + <property name="font"> + <font> + <pointsize>16</pointsize> + </font> + </property> + <property name="styleSheet"> + <string>color:#660033</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Points:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="pointsLabel"> + <property name="styleSheet"> + <string>color:#339999</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="betEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maxLength"> + <number>10</number> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="betButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Bet</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="newGameButton"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>New Game</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="newRoundButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>New Round</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="surrenderButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Surrender</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="hitButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="standButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Stand</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="exitButton"> + <property name="text"> + <string>Exit</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/blackjack/main.cpp b/examples/blackjack/main.cpp new file mode 100644 index 0000000..c2e8105 --- /dev/null +++ b/examples/blackjack/main.cpp @@ -0,0 +1,25 @@ +#include <QApplication> +#include <QFileInfo> +#include "ui_blackjack.h" +#include <QDebug> +#include <QMessageBox> +#include <QUrl> +#include <QScriptEngine> +#include "qscriptedstatemachine.h" +#include "time.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + srand(clock()); + QtScriptedStateMachine *sm = QtScriptedStateMachine::load(":/blackjack.scxml"); + QObject::connect (sm, SIGNAL(finished()), &a, SLOT(quit())); + QMessageBox b; + QWidget* wdg = new QWidget(); + Ui::gameWidget gw; + gw.setupUi(wdg); + sm->registerObject(wdg,"gameWidget",true); + wdg->show(); + sm->start(); + return a.exec(); +} diff --git a/examples/calc/calc.cpp b/examples/calc/calc.cpp new file mode 100644 index 0000000..4b61027 --- /dev/null +++ b/examples/calc/calc.cpp @@ -0,0 +1,35 @@ +#include "calc.h" +#include "ui_calc.h" +#include <QSignalMapper> +CalcWidget::CalcWidget(QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + QSignalMapper* mapper = new QSignalMapper(this); + connect (mapper, SIGNAL(mapped(QString)), this, SIGNAL(command(QString))); + QList<QAbstractButton*> buttons = findChildren<QAbstractButton*>(); + foreach (QAbstractButton* b, buttons) { + connect (b, SIGNAL(clicked()), mapper, SLOT(map())); + } + mapper->setMapping(button0,"DIGIT.0"); + mapper->setMapping(button1,"DIGIT.1"); + mapper->setMapping(button2,"DIGIT.2"); + mapper->setMapping(button3,"DIGIT.3"); + mapper->setMapping(button4,"DIGIT.4"); + mapper->setMapping(button5,"DIGIT.5"); + mapper->setMapping(button6,"DIGIT.6"); + mapper->setMapping(button7,"DIGIT.7"); + mapper->setMapping(button8,"DIGIT.8"); + mapper->setMapping(button9,"DIGIT.9"); + mapper->setMapping(buttonEq,"EQUALS"); + mapper->setMapping(buttonCE,"CE"); + mapper->setMapping(buttonC,"C"); + mapper->setMapping(buttonPoint,"POINT"); + mapper->setMapping(buttonPlus,"OPER.PLUS"); + mapper->setMapping(buttonStar,"OPER.STAR"); + mapper->setMapping(buttonMinus,"OPER.MINUS"); + mapper->setMapping(buttonDiv,"OPER.DIV"); +} +CalcWidget::~CalcWidget() +{ +} diff --git a/examples/calc/calc.h b/examples/calc/calc.h new file mode 100644 index 0000000..6c7cadc --- /dev/null +++ b/examples/calc/calc.h @@ -0,0 +1,19 @@ +#ifndef CALC_H +#define CALC_H + +#include <QtGui/QWidget> +#include "ui_calc.h" + +class CalcWidget : public QWidget, public Ui::CalcClass +{ + Q_OBJECT + +public: + CalcWidget(QWidget *parent = 0); + ~CalcWidget(); + +Q_SIGNALS: + void command(const QString &); +}; + +#endif // CALC_H diff --git a/examples/calc/calc.pro b/examples/calc/calc.pro new file mode 100644 index 0000000..ff274c2 --- /dev/null +++ b/examples/calc/calc.pro @@ -0,0 +1,16 @@ +# ------------------------------------------------- +# Project created by QtCreator 2008-12-25T19:50:44 +# ------------------------------------------------- + +TARGET = calc +TEMPLATE = app +win32: CONFIG += console +mac:CONFIG -= app_bundle +QT = core gui script +include($$PWD/../../src/qtstatemachine.pri) + +# Input +SOURCES += main.cpp calc.cpp +HEADERS += calc.h +FORMS += calc.ui +RESOURCES += calc.qrc diff --git a/examples/calc/calc.qrc b/examples/calc/calc.qrc new file mode 100644 index 0000000..04b1be3 --- /dev/null +++ b/examples/calc/calc.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/" > + <file>calc.scxml</file> + </qresource> +</RCC> diff --git a/examples/calc/calc.scxml b/examples/calc/calc.scxml new file mode 100644 index 0000000..35a9b9e --- /dev/null +++ b/examples/calc/calc.scxml @@ -0,0 +1,213 @@ +<!-- http://www.state-machine.com/devzone/Recipe_DesigningHSM.pdf --> +<!-- events: OPER.PLUS OPER.MINUS OPER.MULTIPLY OPER.DIVIDE DIGIT.0 DIGIT.1_9 EQUALS CE C POINT - +--> +<scxml + initial="on" profile="ecma" name="calc"> + <script> + function insertDigit () + { + insert (_event.name.charAt(_event.name.lastIndexOf('.')+1)); + } + function insert(c) + { + short_expr += c; + updateDisplay(); + } + function negate () + { + short_expr = "-"; + updateDisplay (); + } + + function updateDisplay () + { + if (short_expr == "") + dispLbl.text = _data.res; + else + dispLbl.text = short_expr; + } + function subcalc () + { + if (short_expr != "") + _data.long_expr += "(" + short_expr + ")"; + _data.res = eval(_data.long_expr); + short_expr = ""; + updateDisplay (); + return true; + } + + function insertOp () + { + var sc = subcalc (); + var op = ''; + if (_event.name == "OPER.PLUS") + op = '+'; + else if (_event.name == "OPER.MINUS") + op = '-'; + else if (_event.name == "OPER.STAR") + op = '*'; + else if (_event.name == "OPER.DIV") + op = '/'; + _data.long_expr += op; + return sc; + } + function reset () + { + short_expr = ""; + } + function calc () + { + if (subcalc ()) { + short_expr = "" + _data.res; + _data.long_expr = ""; + _data.res = 0; + return true; + } else + return false; + } + </script> + <state id="on" initial="ready"> + <datamodel> + <data id="long_expr" /> + <data id="res" >0</data> + </datamodel> + <onentry> + <script> + var short_expr = 0; + _data.res = 0; + _data.long_expr = ""; + updateDisplay(); + </script> + </onentry> + <state id="ready" initial="begin"> + <state id="begin"> + <transition event="OPER.MINUS" target="negated1" /> + <onentry> + <script> + updateDisplay (); + </script> + </onentry> + </state> + <state id="result"> + </state> + <transition event="OPER" target="opEntered" /> + <transition event="DIGIT.0" target="zero1"> + <script> + reset (); + </script> + </transition> + <transition event="DIGIT" target="int1"> + <script> + reset (); + </script> + </transition> + <transition event="POINT" target="frac1"> + <script> + reset (); + </script> + </transition> + </state> + <state id="negated1"> + <onentry> + <script> + negate (); + </script> + </onentry> + <transition event="DIGIT.0" target="zero1" /> + <transition event="DIGIT" target="int1" /> + <transition event="POINT" target="frac1" /> + </state> + <state id="operand1"> + <state id="zero1"> + <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int1" /> + <transition event="POINT" target="frac1" /> + </state> + <state id="int1"> + <transition event="POINT" target="frac1" /> + <transition event="DIGIT"> + <script> + insertDigit (); + </script> + </transition> + <onentry> + <script> + insertDigit (); + </script> + </onentry> + </state> + <state id="frac1"> + <onentry> + <script> + insert ('.'); + </script> + </onentry> + <transition event="DIGIT"> + <script> + insertDigit (); + </script> + </transition> + </state> + <transition event="CE" target="ready" /> + <transition event="OPER" target="opEntered" /> + </state> + <state id="error" /> + <state id="opEntered"> + <transition event="OPER.MINUS" target="negated2" /> + <transition event="POINT" target="frac2" /> + <transition event="DIGIT.0" target="zero2" /> + <transition event="DIGIT" target="int2" /> + <onentry> + <script> + insertOp (); + </script> + </onentry> + </state> + <state id="negated2"> + <onentry> + <script> + negate (); + </script> + </onentry> + <transition event="CE" target="opEntered" /> + <transition event="DIGIT.0" target="zero2" /> + <transition event="DIGIT" target="int2" /> + <transition event="POINT" target="frac2" /> + </state> + <state id="operand2"> + <state id="zero2"> + <transition event="DIGIT" cond="_event.name != 'DIGIT.0'" target="int2" /> + <transition event="POINT" target="frac2" /> + </state> + <state id="int2"> + <transition event="DIGIT"> + <script> + insertDigit (); + </script> + </transition> + <onentry> + <script> + insertDigit (); + </script> + </onentry> + <transition event="POINT" target="frac2" /> + </state> + <state id="frac2"> + <onentry> + <script> + insert ('.'); + </script> + </onentry> + <transition event="DIGIT"> + <script> + insertDigit (); + </script> + </transition> + </state> + <transition event="OPER" cond="!insertOp()" target="error" /> + <transition event="OPER" target="opEntered" /> + <transition event="EQUALS" cond="!calc()" target="error" /> + <transition event="EQUALS" target="result" /> + </state> + <transition event="C" target="on" /> + </state> +</scxml> diff --git a/examples/calc/calc.ui b/examples/calc/calc.ui new file mode 100644 index 0000000..0e2a651 --- /dev/null +++ b/examples/calc/calc.ui @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CalcClass</class> + <widget class="QWidget" name="CalcClass"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>419</width> + <height>156</height> + </rect> + </property> + <property name="maximumSize"> + <size> + <width>419</width> + <height>400</height> + </size> + </property> + <property name="windowTitle"> + <string>SCXML Calculator</string> + </property> + <property name="styleSheet"> + <string notr="true">* {background-color:black;color:white} +button {border-width:3; }</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="dispLbl"> + <property name="styleSheet"> + <string notr="true"> +background-color: #CCCCCC; +color: black; +font: 12pt "Courier New";</string> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="horizontalSpacing"> + <number>6</number> + </property> + <item row="3" column="0"> + <widget class="QPushButton" name="button4"> + <property name="text"> + <string>4</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QPushButton" name="button1"> + <property name="text"> + <string>1</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="buttonCE"> + <property name="styleSheet"> + <string notr="true">color:red;font-weight:bold</string> + </property> + <property name="text"> + <string>CE</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="button9"> + <property name="text"> + <string>9</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="button5"> + <property name="text"> + <string>5</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="button7"> + <property name="text"> + <string>7</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="button2"> + <property name="text"> + <string>2</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QPushButton" name="button0"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QPushButton" name="button6"> + <property name="text"> + <string>6</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QPushButton" name="button3"> + <property name="text"> + <string>3</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QPushButton" name="buttonPoint"> + <property name="text"> + <string>.</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QPushButton" name="buttonPlus"> + <property name="text"> + <string>+</string> + </property> + </widget> + </item> + <item row="3" column="4"> + <widget class="QPushButton" name="buttonMinus"> + <property name="text"> + <string>-</string> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QPushButton" name="buttonStar"> + <property name="text"> + <string>*</string> + </property> + </widget> + </item> + <item row="4" column="4"> + <widget class="QPushButton" name="buttonDiv"> + <property name="text"> + <string>/</string> + </property> + </widget> + </item> + <item row="5" column="3" colspan="2"> + <widget class="QPushButton" name="buttonEq"> + <property name="text"> + <string>=</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="button8"> + <property name="text"> + <string>8</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QPushButton" name="buttonC"> + <property name="styleSheet"> + <string notr="true">color: red; font-weight:bold</string> + </property> + <property name="text"> + <string>C</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/examples/calc/main.cpp b/examples/calc/main.cpp new file mode 100644 index 0000000..9d30c6b --- /dev/null +++ b/examples/calc/main.cpp @@ -0,0 +1,15 @@ + +#include <QtGui/QApplication> +#include "calc.h" +#include "qscriptedstatemachine.h" +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QtScriptedStateMachine *sm = QtScriptedStateMachine::load(":/calc.scxml"); + CalcWidget w; + sm->registerObject(&w,"",true); + QObject::connect (&w, SIGNAL(command(QString)), sm, SLOT(postNamedEvent(QString))); + w.show(); + sm->start(); + return a.exec(); +} diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 0000000..a13e798 --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += blackjack calc mediaplayer
\ No newline at end of file diff --git a/examples/mediaplayer/main.cpp b/examples/mediaplayer/main.cpp new file mode 100644 index 0000000..1eb7b4b --- /dev/null +++ b/examples/mediaplayer/main.cpp @@ -0,0 +1,58 @@ +#include "qscriptedstatemachine.h" +#include "spview.h" +#include "spmodel.h" +#include "spengine.h" +#include "spharvester.h" +#include "math.h" +#include "time.h" +#include <QDebug> +#include <QApplication> +#include <QLabel> +#include <QPushButton> +#include <QScriptEngine> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QFileInfo> +#include <QScriptContext> +#include <QScriptEngine> +#include <QMenu> +#include <QMainWindow> + + +int main( int argc, char **argv) +{ + QApplication app(argc, argv); + QString dir; + bool recurse; + if (argc > 1) { + dir = QString(argv[1]); + if (argc > 2) + { + recurse = !strcmp(argv[2],"-recurse"); + } + } else { + printf("Usage: stateplayer directory [-recurse]"); + return 0; + } + + app.setApplicationName("SCXML-mediaplayer"); + + SPView* view = new SPView(NULL); + QtScriptedStateMachine *sm = QtScriptedStateMachine::load(":/mediaplayer.scxml"); + QObject::connect (sm, SIGNAL(finished()), &app, SLOT(quit())); + SPModel* model= new SPModel(NULL); + view->setModel(model); + model->setObjectName("model"); + SPEngine* engine = new SPEngine(sm); + engine->setObjectName("engine"); + SPHarvester* harvester = new SPHarvester (view); + QObject::connect (harvester, SIGNAL(foundTrack(SongData)), model, SLOT(addSong(SongData))); + harvester->harvest(dir,recurse); + view->setObjectName("view"); + sm->registerObject(model); + sm->registerObject(engine); + sm->registerObject(view,"",true); + view->show (); + sm->start (); + return app.exec (); +} diff --git a/examples/mediaplayer/mediaplayer.pro b/examples/mediaplayer/mediaplayer.pro new file mode 100644 index 0000000..355da25 --- /dev/null +++ b/examples/mediaplayer/mediaplayer.pro @@ -0,0 +1,23 @@ +TEMPLATE = app +TARGET = scxmlplayer +QT += script \ + sql \ + phonon +include($$PWD/../../src/qtstatemachine.pri) + +HEADERS += spmodel.h \ + spengine.h \ + spview.h \ + spharvester.h \ + songdata.h +SOURCES += main.cpp \ + spmodel.cpp \ + spengine.cpp \ + spview.cpp \ + spharvester.cpp +FORMS += mediaplayer.ui +RESOURCES += mediaplayer.qrc +win32:CONFIG += console +mac:CONFIG -= app_bundle +INCLUDEPATH += . +DEPENDPATH += . diff --git a/examples/mediaplayer/mediaplayer.qrc b/examples/mediaplayer/mediaplayer.qrc new file mode 100644 index 0000000..ff8226f --- /dev/null +++ b/examples/mediaplayer/mediaplayer.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource> + <file>mediaplayer.scxml</file> + </qresource> +</RCC> diff --git a/examples/mediaplayer/mediaplayer.scxml b/examples/mediaplayer/mediaplayer.scxml new file mode 100644 index 0000000..323d9c9 --- /dev/null +++ b/examples/mediaplayer/mediaplayer.scxml @@ -0,0 +1,240 @@ +<scxml xmlns="http://www.w3.org/2005/07/scxml" initial="root"> + <parallel id="root"> + <state initial="gui"> + <invoke targettype="q-bindings"> + <content>[[listView,'styleSheet','QListView {font-size:20px}']]</content> + </invoke> + <state id="gui" initial="menu_tree"> + <transition event="q-signal:backButton.clicked()" anchor="lastmenupos" /> + <transition event="q-signal:homeButton.clicked()" target="mainmenu" /> + <state id="menu_tree" initial="mainmenu"> + <invoke type="q-binding"><content>[[stackedWidget,"currentIndex",0]]</content></invoke> + <state id="mainmenu"> + <anchor type="lastmenupos" /> + <onentry><script> + homeButton.enabled = false; + selectButton.enabled = false; + </script></onentry> + <onexit><script> + homeButton.enabled = true; + selectButton.enabled = true; + </script></onexit> + <invoke type="q-menu"> + <content> + { "parent" : listView, "windowTitle" : + "Music Player", "styleSheet" : "QMenu {font-size:24px; width: 505;height:1000;}" + + "QMenu::item:hover {background-color: blue;color: black;}", + "children": function() { var c = [ + {"type" : "action","id" : "artists", "text" : "Artists" }, + {"type" : "action", "id" : "albums", "text" : "Albums" }, + {"type" : "action", "id" : "genres", "text" : "Genres" }, + {"type" : "action", "id" : "allsongs", "text" : "All Songs" }]; + if (model.currentSongTitle != '') + c[c.length] = {type: "action", id: "curplaying", text: model.currentSongTitle}; + return c; }() + }</content> + </invoke> + <transition event="menu.action.artists" target="artists" /> + <transition event="menu.action.albums" target="albums"> + <script>model.clearAlbumFilter ();</script> + </transition> + <transition event="menu.action.genres" target="genres" /> + <transition event="menu.action.curplaying" target="playingwin" /> + <transition event="menu.action.allsongs" target="songs"> + <script>model.clearSongFilter ();</script> + </transition> + </state> + <state id="artists"> + <anchor type="lastmenupos" /> + <onentry> + <script>model.loadArtists (); view.showArtists();</script> + </onentry> + <transition event="q-signal:model.artistChanged(QString)"> + + <script>model.loadArtists();</script> + </transition> + <transition event="q-signal:selectButton.clicked()" target="albums" cond="view.currentIndex >=0"> + <script> + model.filterAlbumsByArtist(view.currentItem); + </script> + </transition> + </state> + <state id="albums"> + <onentry> + <script>model.loadAlbums ();</script> + </onentry> + <transition cond="model.albumCount==1" target="songs" /> + <transition cond="model.albumCount > 1" target="show_albums" /> + <transition cond="model.albumCount==0" target="songs" /> + </state> + <state id="show_albums"> + <anchor type="lastmenupos" /> + <transition event="q-signal:model.albumChanged(QString)"> + + <script>model.loadAlbums();</script> + </transition> + <transition event="q-signal:selectButton.clicked()" cond="view.currentIndex >=0" target="songs"> + <script> + model.filterSongsByAlbum(view.currentItem);</script> + </transition> + <onentry> + <script>view.showAlbums ();</script> + </onentry> + </state> + <state id="genres"> + <anchor type="lastmenupos" /> + <onentry> + <script>model.loadGenres (); view.showGenres + ();</script> + </onentry> + <transition event="q-signal:model.genreChanged(QString)"> + + <script>model.loadGenres();</script> + </transition> + <transition event="q-signal:selectButton.clicked()" cond="view.currentIndex >=0" target="songs"> + <script> + model.filterSongsByGenre(view.currentItem);</script> + </transition> + </state> + <state id="songs"> + <anchor type="lastmenupos" /> + <onentry> + <script>model.loadSongs (); view.showSongs + ();</script> + </onentry> + <transition event="q-signal:model.songListChanged()"> + <script>model.loadSongs();</script> + </transition> + <transition event="q-signal:selectButton.clicked()" cond="view.currentIndex >=0" target="playingwin"> + <script> + model.selectSong (view.currentItem); + engine.setTrack(model.currentSong); + </script> + <raise event="playIntent" /> + <raise event="songSelected" /> + </transition> + </state> + </state> + <state id="playingwin"> + <anchor type="lastmenupos" /> + <invoke type="q-bindings"> + <content>[ + [selectButton,"enabled",false], + [playingLabel,"text",model.currentSongTitle], + [midLabel,"text",model.currentSongArtist], + [posSlider,"minimum",0], + [posSlider,"maximum",engine.totalTime], + [stackedWidget,"currentIndex",1] + ]</content> + </invoke> + <onentry> + <script>view.showPlayer ();</script> + </onentry> + <transition event="q-signal:model.songChanged()"> + <script> + midLabel.text = model.currentSongArtist + ' / ' + model.currentSongAlbum; + playingLabel.text = model.currentSongTitle; + </script> + </transition> + </state> + </state> + </state> + <state id="engine" initial="idle"> + <onentry><script> + volumeSlider.value = engine.volume; + </script></onentry> + + <transition event="q-signal:model.songChanged()"> + <script>engine.setTrack(model.currentSong);</script> + <raise event="playIntent" /> + <raise event="songChanged" /> + </transition> + <state id="idle"> + <transition event="playIntent" target="playing" /> + <transition event="q-signal:playButton.clicked()"> + <raise event="playIntent" /> + </transition> + <invoke type="q-bindings"> + <content>[[stopButton,"enabled",false]]</content> + </invoke> + </state> + <state id="active" initial="playing"> + <invoke type="q-bindings"> + <content>[[stopButton,"enabled",true]]</content> + </invoke> + <transition event="q-signal:stopButton.clicked()" + target="idle"> + <script>engine.stop ();</script> + </transition> + <state id="playing"> + <invoke type="q-bindings"> + <content>[[playButton,"text","Pause"]]</content> + </invoke> + <onentry> + <script>engine.play ();</script> + </onentry> + <transition event="q-signal:playButton.clicked()" + target="paused" /> + <transition event="songChanged"> + <script>engine.play();</script> + </transition> + <transition event="q-signal:engine.tick(qint64)"> + <script>view.setCurrentTime(_event.data[0]);</script> + </transition> + <transition event="q-signal:engine.totalTimeChanged(qint64)"> + <script>view.setTotalTime(_event.data[0]);</script> + </transition> + </state> + <state id="paused"> + <onentry> + <script>engine.pause();</script> + </onentry> + <transition event="q-signal:playButton.clicked()" + target="playing" /> + </state> + <transition event="q-signal:model.endOfList()" + target="idle"> + <script>engine.stop (); model.reset ();</script> + </transition> + </state> + <transition event="q-signal:engine.aboutToFinish()"> + <raise event="nextSong" /> + </transition> + <transition event="nextSong"> + <script> + model.next(); + engine.enqueue(model.currentSong); + </script> + </transition> + <transition event="q-signal:nextButton.clicked()"> + <script>model.gotoNext();</script> + </transition> + <transition event="q-signal:prevButton.clicked()"> + <script>model.gotoPrev();</script> + </transition> + <transition event="q-signal:posSlider.sliderMoved(int)"> + <script>engine.seek(_event.data[0]);</script> + </transition> + <transition event="q-signal:volumeSlider.sliderMoved(int)"> + <script>engine.volume = _event.data[0];</script> + </transition> + <transition event="q-signal:engine.volumeChanged(int)"> + <script>volumeSlider.value = _event.data[0];</script> + </transition> + </state> + <state id="selection_state" initial="no_song_selected"> + <state id="no_song_selected"> + <transition event="songSelected" target="song_selected" /> + <invoke type="q-bindings"> + <content>[[stopButton,"enabled",false], + [playButton,"enabled",false], + [prevButton,"enabled",false], + [nextButton,"enabled",false]]</content> + </invoke> + </state> + <state id="song_selected"> + <transition event="endOfList" target="no_song_selected" /> + </state> + </state> + </parallel> +</scxml> diff --git a/examples/mediaplayer/mediaplayer.ui b/examples/mediaplayer/mediaplayer.ui new file mode 100644 index 0000000..dbbc6fd --- /dev/null +++ b/examples/mediaplayer/mediaplayer.ui @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>mediaPlayerWidget</class> + <widget class="QWidget" name="mediaPlayerWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>394</width> + <height>287</height> + </rect> + </property> + <property name="windowTitle"> + <string>SCXML Media Player</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="page"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QListView" name="listView"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="playerPage"> + <widget class="QLabel" name="playingLabel"> + <property name="geometry"> + <rect> + <x>0</x> + <y>50</y> + <width>291</width> + <height>61</height> + </rect> + </property> + <property name="styleSheet"> + <string>QLabel {font-size: 24px; color: #336699}</string> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + <widget class="QLabel" name="midLabel"> + <property name="geometry"> + <rect> + <x>0</x> + <y>120</y> + <width>291</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + <widget class="QSlider" name="posSlider"> + <property name="geometry"> + <rect> + <x>0</x> + <y>180</y> + <width>301</width> + <height>16</height> + </rect> + </property> + <property name="maximum"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </widget> + </widget> + </item> + <item> + <widget class="QWidget" name="widget1" native="true"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QToolButton" name="homeButton"> + <property name="text"> + <string>Home</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="playButton"> + <property name="text"> + <string>Play</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="prevButton"> + <property name="text"> + <string>Prev</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="nextButton"> + <property name="text"> + <string>Next</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="stopButton"> + <property name="text"> + <string>Stop</string> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="volumeSlider"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="selectButton"> + <property name="text"> + <string>Select</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="backButton"> + <property name="text"> + <string>Back</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/examples/mediaplayer/songdata.h b/examples/mediaplayer/songdata.h new file mode 100644 index 0000000..0c31f03 --- /dev/null +++ b/examples/mediaplayer/songdata.h @@ -0,0 +1,15 @@ +#ifndef SONGDATA_H +#define SONGDATA_H + +#include <QStringList> + +struct SongData +{ + QString url; + QString title; + QString album; + QString artist; + QStringList genres; + int trackNumber; +}; +#endif diff --git a/examples/mediaplayer/spengine.cpp b/examples/mediaplayer/spengine.cpp new file mode 100644 index 0000000..9d8132f --- /dev/null +++ b/examples/mediaplayer/spengine.cpp @@ -0,0 +1,84 @@ +#include "spengine.h" +#include <phonon> + +using namespace Phonon; + +class SPEnginePvt +{ + public: + MediaObject* mediaObject; + AudioOutput* audioOutput; +}; +void SPEngine::clearQueue() +{ + pvt->mediaObject->clearQueue(); +} + +int SPEngine::currentTime() const +{ + return pvt->mediaObject->currentTime (); +} +int SPEngine::totalTime() const +{ + return pvt->mediaObject->totalTime(); +} + +void SPEngine::enqueue (const QUrl & u) +{ + pvt->mediaObject->enqueue(MediaSource(u)); +} +void SPEngine::setTrack(const QUrl & u) +{ + pvt->mediaObject->setCurrentSource(MediaSource(u)); +} +void SPEngine::play() +{ + pvt->mediaObject->play (); +} + +void SPEngine::pause() +{ + pvt->mediaObject->pause (); +} + +void SPEngine::stop() +{ + pvt->mediaObject->stop(); +} + +void SPEngine::seek(qint64 pos) +{ + pvt->mediaObject->seek(pos); +} + +void SPEngine::setVolume(int v) +{ + pvt->audioOutput->setVolume((qreal)v/100); +} + +void SPEngine::onVolumeChanged(qreal r) +{ + emit volumeChanged(r*100); +} +int SPEngine::volume() const +{ + return pvt->audioOutput->volume()*100; +} + +SPEngine::SPEngine(QObject* p) : QObject(p) +{ + pvt = new SPEnginePvt; + pvt->mediaObject = new Phonon::MediaObject(this); + pvt->audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); + createPath (pvt->mediaObject, pvt->audioOutput); + pvt->mediaObject->setTickInterval(500); + connect (pvt->mediaObject, SIGNAL(aboutToFinish()), this, SIGNAL(aboutToFinish())); + connect (pvt->mediaObject, SIGNAL(tick(qint64)), this, SIGNAL(tick(qint64))); + connect (pvt->mediaObject, SIGNAL(totalTimeChanged(qint64)), this, SIGNAL(totalTimeChanged(qint64))); + connect (pvt->audioOutput, SIGNAL(volumeChanged(qreal)), this, SLOT(onVolumeChanged(qreal))); +} + +SPEngine::~SPEngine () +{ + delete pvt; +} diff --git a/examples/mediaplayer/spengine.h b/examples/mediaplayer/spengine.h new file mode 100644 index 0000000..3134b75 --- /dev/null +++ b/examples/mediaplayer/spengine.h @@ -0,0 +1,41 @@ +#ifndef SPENGINE_H +#define SPENGINE_H +#include <QObject> +#include <QUrl> +class SPEngine : public QObject +{ + Q_OBJECT + Q_PROPERTY(int totalTime READ totalTime) + Q_PROPERTY(int currentTime READ currentTime) + Q_PROPERTY(int volume READ volume WRITE setVolume) + + Q_SIGNALS: + void aboutToFinish(); + void tick(qint64); + void totalTimeChanged(qint64); + void volumeChanged(int); + + public Q_SLOTS: + void clearQueue(); + void enqueue (const QUrl &); + void setTrack(const QUrl &); + void play(); + void pause(); + void seek(qint64); + void stop (); + + protected Q_SLOTS: + void onVolumeChanged(qreal); + + public: + SPEngine(QObject*); + virtual ~SPEngine (); + int currentTime () const; + int totalTime () const; + void setVolume(int); + int volume () const; + + private: + class SPEnginePvt* pvt; +}; +#endif diff --git a/examples/mediaplayer/spharvester.cpp b/examples/mediaplayer/spharvester.cpp new file mode 100644 index 0000000..ee6294f --- /dev/null +++ b/examples/mediaplayer/spharvester.cpp @@ -0,0 +1,68 @@ +#include "spharvester.h" +#include <QDir> +#include <phonon> +#include <QQueue> +#include <QFile> +#include <QUrl> + +using namespace Phonon; + +struct SPHarvesterPvt +{ + MediaObject* mediaObject; + QQueue<QString> pathQueue; +}; + +SPHarvester::SPHarvester(QObject* o) : QObject(o) +{ + pvt = new SPHarvesterPvt; + pvt->mediaObject = new MediaObject(this); + connect (pvt->mediaObject, SIGNAL(metaDataChanged()), this, SLOT(readMetaData ())); +} + +SPHarvester::~SPHarvester() +{ + delete pvt; +} + +void SPHarvester::harvest (const QString & directory, bool recurse) +{ + QDir d (directory); + QFileInfoList l = d.entryInfoList(QStringList() << "*.mp3",recurse ? QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files : QDir::Files); + foreach (QFileInfo fi, l) { + if (fi.isDir()) { + harvest (fi.absoluteFilePath(), recurse); + } else { + pvt->pathQueue.enqueue(fi.absoluteFilePath()); + } + } + harvestNext (); +} + +void SPHarvester::harvestNext () +{ + if (pvt->pathQueue.empty()) + emit done(); + else { + QString s = pvt->pathQueue.dequeue(); + pvt->mediaObject->setCurrentSource(MediaSource(s)); + } +} + +void SPHarvester::readMetaData () +{ + QStringList albums = pvt->mediaObject->metaData("ALBUM"); + QStringList titles = pvt->mediaObject->metaData("TITLE"); + QStringList artists = pvt->mediaObject->metaData("ARTIST"); + QStringList trackNums = pvt->mediaObject->metaData("TRACKNUMBER"); + SongData sd; + sd.url = pvt->mediaObject->currentSource().url().toString(); + sd.album = albums.count() ? albums[0] : "Unknown Album"; + sd.artist = artists.count() ? artists[0] : "Unknown Artist"; + sd.trackNumber = trackNums.count() ? trackNums[0].toInt() : 0; + sd.genres = pvt->mediaObject->metaData("GENRE"); + sd.title = titles.count() ? titles[0] : QFileInfo(sd.url).baseName(); + + emit foundTrack(sd); + harvestNext (); +} diff --git a/examples/mediaplayer/spharvester.h b/examples/mediaplayer/spharvester.h new file mode 100644 index 0000000..8fbe583 --- /dev/null +++ b/examples/mediaplayer/spharvester.h @@ -0,0 +1,25 @@ +#ifndef SPHARVESTER_H +#define SPHARVESTER_H + +#include "songdata.h" +class SPHarvester : public QObject +{ + Q_OBJECT +public: + SPHarvester(QObject* o = NULL); + virtual ~SPHarvester (); +public slots: + void harvest (const QString & directory, bool recurse = true); + +private slots: + void harvestNext (); + void readMetaData(); +signals: + void foundTrack (const SongData & d); + void done (); + +private: + class SPHarvesterPvt* pvt; +}; + +#endif // _H diff --git a/examples/mediaplayer/spmodel.cpp b/examples/mediaplayer/spmodel.cpp new file mode 100644 index 0000000..46e614d --- /dev/null +++ b/examples/mediaplayer/spmodel.cpp @@ -0,0 +1,274 @@ +#include "spmodel.h" +#include <QSqlQueryModel> +#include <QtSql> +#include <QStandardItemModel> +#include <QMessageBox> +// an SQL query model that always as column (0) as uid and column (1) as display +class SPSqlQueryModel : public QSqlQueryModel +{ + Q_OBJECT + public: + SPSqlQueryModel (QObject* o = NULL): QSqlQueryModel (o) {} + virtual QVariant data(const QModelIndex & index, int role) const + { + QModelIndex idx(index); + if (role == Qt::DisplayRole && query().record().count() > 1) { + idx = idx.sibling(idx.row(),1); + } else if (role == Qt::UserRole) + role = Qt::DisplayRole; + return QSqlQueryModel::data(idx,role); + } +}; + + +class SPModelPvt +{ + public: + SPSqlQueryModel + artistModel, + albumModel, + songModel, + playlistModel, genreModel; + + QSqlQuery artistQuery, albumQuery, songQuery, playlistQuery, genreQuery, playingQuery; + +}; + +SPModel::SPModel(QObject* o) + :QObject(o) +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("sqlite.db"); + if (!db.open()) { + QMessageBox::critical(0, qApp->tr("Cannot open database"), + qApp->tr("Unable to establish a database connection.\n" + "This example needs SQLite support. Please read " + "the Qt SQL driver documentation for information how " + "to build it.\n\n" + "Click Cancel to exit."), QMessageBox::Cancel); + } + + db.exec("CREATE TABLE IF NOT EXISTS songs (song_url VARCHAR(1024) PRIMARY KEY, song_title VARCHAR(1024), song_artist VARCHAR(1024), song_album VARCHAR(1024), song_track_index SMALLINT)"); + db.exec("DROP TABLE genres"); + db.exec("CREATE TABLE IF NOT EXISTS genres (genre_title VARCHAR(64), genre_song_url VARCHAR(1024))"); +// db.exec("CREATE TABLE IF NOT EXISTS playlists (playlist_id VARACHAR(256) PRIMARY KEY, playlist_title VARCHAR(1024))"); +// db.exec("CREATE TABLE IF NOT EXISTS playlist_songs (playlist_song_id BIGINT PRIMARY KEY, playlist_song_playlist + + pvt = new SPModelPvt; + pvt->artistQuery = QSqlQuery("SELECT DISTINCT song_artist FROM songs"); + pvt->albumQuery = QSqlQuery("SELECT DISTINCT song_album FROM songs"); + pvt->playlistQuery = QSqlQuery("SELECT playlist_url, playlist_title FROM playlists"); + pvt->songQuery = QSqlQuery("SELECT song_url, song_title from songs"); + pvt->genreQuery = QSqlQuery("SELECT DISTINCT genre_title from genres"); + QSqlQuery q; +// q.exec("SELECT DISTINCT song_url,song_title FROM songs, genres WHERE genre_song_url=song_url AND genre_title='All'"); + q.exec("SELECT * from genres"); + while (q.next()) { + qDebug () << q.value(0); + } +} + +void SPModel::addSong ( const SongData & data) +{ + + QSqlQuery q; + q.prepare("SELECT count(*) FROM songs WHERE song_url=:url"); + q.bindValue(":url",data.url); + bool inserting = true; + if (q.exec()) { + q.next(); + inserting = q.value(0).toInt() == 0; + } + if (inserting) { + q.prepare ("INSERT INTO songs (song_url, song_title, song_artist, song_album, song_track_index) VALUES (:url, :title, :artist, :album, :track)"); + } else { + q.prepare("UPDATE songs SET song_title=:title, song_album=:album, song_track_index=:track WHERE song_url=:url "); + } + q.bindValue(":url",data.url); + q.bindValue(":title",data.title); + q.bindValue(":artist",data.artist); + q.bindValue(":album",data.album); + q.bindValue(":track",data.trackNumber); + q.exec(); + + q.prepare ("DELETE FROM genres WHERE genre_song_url=:url"); + q.bindValue(":url",data.url); + q.exec(); + + q.prepare ("INSERT INTO genres (genre_song_url, genre_title) VALUES(:url, :genre)"); + q.bindValue(":url",data.url); + QStringList gn = data.genres; + gn << "All"; + foreach (QString g, gn) { + q.bindValue(":genre",g); + q.exec (); + } + + if (inserting) { + emit albumChanged(data.album); + emit songListChanged(); + emit artistChanged(data.artist); + foreach (QString g, data.genres) { + emit genreChanged(g); + } + } + + +} + + +SPModel::~SPModel() +{ + delete pvt; +} + +int SPModel::albumCount() const +{ + return pvt->albumModel.rowCount(); +} + +void SPModel::clearAlbumFilter () +{ + pvt->albumQuery = QSqlQuery ("SELECT DISTINCT song_album FROM songs "); +} +void SPModel::clearSongFilter () +{ + pvt->songQuery = QSqlQuery ("SELECT song_url, song_title FROM songs"); +} +void SPModel::loadArtists () +{ + pvt->artistQuery.exec (); + pvt->artistModel.setQuery(pvt->artistQuery); +} +void SPModel::filterAlbumsByArtist(const QString & artist) +{ + pvt->albumQuery.prepare("SELECT DISTINCT song_album FROM songs WHERE song_artist=:artist"); + pvt->albumQuery.bindValue(":artist",artist); +} +void SPModel::filterSongsByAlbum(const QString & album) +{ + pvt->albumQuery.prepare("SELECT song_url,song_title, song_track_index FROM songs WHERE song_album=:album ORDER BY song_track_index"); + pvt->albumQuery.bindValue(":album",album); +} +void SPModel::loadGenres () +{ + pvt->genreQuery.exec(); + pvt->genreModel.setQuery(pvt->genreQuery); +} + +void SPModel::filterSongsByGenre(const QString & genre) +{ + pvt->songQuery.prepare ("SELECT DISTINCT song_url,song_title FROM songs, genres WHERE genre_song_url=song_url AND genre_title=:genre"); + pvt->songQuery.bindValue(":genre",genre); +} +void SPModel::loadPlaylists() +{ + pvt->playlistQuery.exec (); + pvt->playlistModel.setQuery(pvt->playlistQuery); +} +void SPModel::loadAlbums() +{ + pvt->albumQuery.exec (); + pvt->albumModel.setQuery(pvt->albumQuery); +} +void SPModel::filterSongsByPlaylist(const QString & uid) +{ + pvt->songQuery.prepare("SELECT DISTINCT song_url, song_title, playlist_song_index FROM playlist_songs INNER JOIN songs ON playlist_song_url=song_url WHERE playlist_id=:playlist ORDER BY playlist_song_index"); + pvt->songQuery.bindValue(":playlist",uid); +} +void SPModel::loadSongs () +{ + pvt->songQuery.exec (); + pvt->songModel.setQuery(pvt->songQuery); +} +QUrl SPModel::currentSong() +{ + if (pvt->playingQuery.isValid()) + return QUrl(pvt->playingQuery.value(0).toString()); + else + return QUrl(); +} +QString SPModel::currentSongTitle() +{ + if (pvt->playingQuery.isValid()) + return pvt->playingQuery.value(1).toString(); + else + return QString(); +} +QString SPModel::currentSongArtist() +{ + QSqlQuery q; + q.prepare("SELECT song_artist FROM songs WHERE song_url=:url"); + q.bindValue(":url",currentSong().toString()); + q.exec(); + q.next(); + return q.value(0).toString(); +} +QString SPModel::currentSongAlbum() +{ + QSqlQuery q; + q.prepare("SELECT song_album FROM songs WHERE song_url=:url"); + q.bindValue(":url",currentSong().toString()); + q.exec(); + q.next(); + return q.value(0).toString(); +} + +void SPModel::selectSong (const QString & s) +{ + pvt->playingQuery = QSqlQuery(pvt->songQuery.executedQuery ()); + + while (pvt->playingQuery.next()) { + if (pvt->playingQuery.value(0).toString() == s) { + emit songChanged (); + return; + } + } + emit endOfList (); +} + +void SPModel::reset () +{ + pvt->playingQuery = QSqlQuery(pvt->songQuery.executedQuery ()); + pvt->playingQuery.exec(); +} + +void SPModel::gotoNext() +{ + if (pvt->playingQuery.next()) { + emit songChanged (); + }else + emit endOfList (); +} +void SPModel::gotoPrev() +{ + if (pvt->playingQuery.previous()) + emit songChanged (); + else + emit endOfList (); +} + + + +QAbstractItemModel* SPModel::albumsItemModel() const +{ + return &pvt->albumModel; +} +QAbstractItemModel* SPModel::genresItemModel() const +{ + return &pvt->genreModel; +} +QAbstractItemModel* SPModel::songsItemModel() const +{ + return &pvt->songModel; +} +QAbstractItemModel* SPModel::playlistsItemModel() const +{ + return &pvt->playlistModel; +} +QAbstractItemModel* SPModel::artistsItemModel() const +{ + return &pvt->artistModel; +} + +#include <spmodel.moc> diff --git a/examples/mediaplayer/spmodel.h b/examples/mediaplayer/spmodel.h new file mode 100644 index 0000000..af8254b --- /dev/null +++ b/examples/mediaplayer/spmodel.h @@ -0,0 +1,64 @@ +#ifndef SPMODEL_H +#define SPMODEL_H +#include <QObject> +#include <QUrl> +#include <QAbstractItemModel> +#include "songdata.h" + +class SPModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl currentSong READ currentSong) + Q_PROPERTY(QString currentSongTitle READ currentSongTitle) + Q_PROPERTY(QString currentSongArtist READ currentSongArtist) + Q_PROPERTY(QString currentSongAlbum READ currentSongAlbum) + Q_PROPERTY(int albumCount READ albumCount) + + + public slots: + void clearAlbumFilter (); + void clearSongFilter (); + void loadArtists (); + void filterAlbumsByArtist(const QString & name); + void loadGenres (); + void filterSongsByGenre(const QString & genre); + void loadPlaylists(); + void loadAlbums(); + void filterSongsByPlaylist(const QString & uid); + void filterSongsByAlbum(const QString & name); + void loadSongs (); + void selectSong (const QString &); + void gotoNext(); + void gotoPrev(); + void addSong (const SongData &); + void reset (); + + signals: + void albumChanged(const QString &); + void artistChanged(const QString &); + void genreChanged(const QString &); + void songListChanged(); + void songChanged (); + void endOfList (); + + public: + SPModel(QObject*); + virtual ~SPModel (); + + QUrl currentSong(); + QString currentSongTitle (); + QString currentSongArtist(); + QString currentSongAlbum(); + QAbstractItemModel* albumsItemModel() const; + QAbstractItemModel* genresItemModel() const; + QAbstractItemModel* songsItemModel() const; + QAbstractItemModel* playlistsItemModel() const; + QAbstractItemModel* artistsItemModel() const; + int albumCount() const; + + private: + class SPModelPvt* pvt; +}; + +#endif diff --git a/examples/mediaplayer/spview.cpp b/examples/mediaplayer/spview.cpp new file mode 100644 index 0000000..1d52053 --- /dev/null +++ b/examples/mediaplayer/spview.cpp @@ -0,0 +1,97 @@ +#include "spview.h" +#include "spmodel.h" +#include <QDebug> +#include <QItemDelegate> + +class SPViewPvt +{ + public: + SPModel* model; +}; + + +class SPItemDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + SPItemDelegate(QObject* o) : QItemDelegate(o) {} + + virtual void paint (QPainter* p, QStyleOptionViewItem & option, const QModelIndex & index) const + { + QString disp = index.data(Qt::DisplayRole).toString(); + drawBackground(p,option,index); + drawDisplay(p,option,option.rect,disp); + } +}; + +void SPView::setModel (SPModel* m) +{ + pvt->model = m; +} + +void SPView::showAlbums () +{ + listView->setModel (pvt->model->albumsItemModel()); +} + +void SPView::showArtists () +{ + listView->setModel (pvt->model->artistsItemModel()); +} + +void SPView::showGenres () +{ + QAbstractItemModel* model = pvt->model->genresItemModel(); + listView->setModel (model); +} + +void SPView::showSongs () +{ + listView->setModel (pvt->model->songsItemModel()); +} + +void SPView::showPlaylists() +{ + listView->setModel (pvt->model->playlistsItemModel()); +} + + +SPView::SPView(QWidget* w) : QWidget (w) +{ + pvt = new SPViewPvt; + setupUi(this); + listView->setItemDelegate(new SPItemDelegate(this)); +} + +QString SPView::currentItem() const +{ + QVariant v = listView->model()->data(listView->currentIndex(),Qt::UserRole); + if (v.isNull()) + v = listView->currentIndex().data(Qt::DisplayRole); + return v.toString (); +} + +int SPView::itemCount () const +{ + return listView->model()->rowCount (); +} +int SPView::currentIndex() const +{ + return listView->currentIndex().row(); +} + +void SPView::setTotalTime (int t) +{ + posSlider->setMaximum(t); +} +void SPView::setCurrentTime (int t) +{ + posSlider->setValue (t); +} + +SPView::~SPView () +{ + delete pvt; +} +#include "spview.moc" diff --git a/examples/mediaplayer/spview.h b/examples/mediaplayer/spview.h new file mode 100644 index 0000000..53b8c09 --- /dev/null +++ b/examples/mediaplayer/spview.h @@ -0,0 +1,36 @@ +#ifndef SPVIEW_H +#define SPVIEW_H +#include <QObject> +#include <QUrl> +#include "spmodel.h" +#include "ui_mediaplayer.h" + +class SPView : public QWidget, public virtual Ui::mediaPlayerWidget +{ + Q_OBJECT + Q_PROPERTY(QString currentItem READ currentItem) + Q_PROPERTY(int itemCount READ itemCount) + Q_PROPERTY(int currentIndex READ currentIndex) + public slots: + void setModel (SPModel*); + void showAlbums (); + void showArtists (); + void showGenres (); + void showSongs (); + void showPlaylists(); + void setTotalTime (int); + void setCurrentTime (int); + + + public: + SPView(QWidget*); + virtual ~SPView (); + QString currentItem () const; + int itemCount () const; + int currentIndex() const; + + private: + class SPViewPvt* pvt; +}; + +#endif @@ -1,12 +1,2 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2009-05-24T14:33:48 -# -#------------------------------------------------- - -QT += script -QT -= gui -TARGET = qscxml -TEMPLATE = staticlib -SOURCES += qscxml.cpp -HEADERS += qscxml.h +TEMPLATE = subdirs +SUBDIRS += src examples
\ No newline at end of file diff --git a/qscxml.cpp b/src/qscxml.cpp index 741dc33..8190056 100644 --- a/qscxml.cpp +++ b/src/qscxml.cpp @@ -56,7 +56,6 @@ #include "qscxml.h" #include <QScriptEngine> #include <QScriptValueIterator> -#include "qstatefinishedevent.h" #include <QDebug> #include <QTimer> #include <QSignalMapper> @@ -67,12 +66,11 @@ #include <QDir> #include <QSet> #include <QStack> -#include "qhistorystate.h" -#include "qabstracttransition_p.h" -#include "qfinalstate.h" -#include "qabstractstate.h" +#include <QHistoryState> +#include <QFinalState> +#include <QState> #ifdef QT_GUI_LIB -#include "qscxmlguiinvokers_p.h" +#include "qscxmlgui.h" #endif @@ -443,7 +441,7 @@ QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine) /*! \internal */ -bool QScxmlTransition::eventTest(QEvent *e) const +bool QScxmlTransition::eventTest(QEvent *e) { QScriptEngine* engine = scxml->scriptEngine(); QString ev; @@ -451,8 +449,6 @@ bool QScxmlTransition::eventTest(QEvent *e) const if (e) { if (e->type() == QScxmlEvent::eventType()) { ev = ((QScxmlEvent*)e)->eventName(); - } else if (e->type() == QEvent::Type(QEvent::User-2)) { - ev = QString("done.state.") + ((QStateFinishedEvent*)e)->state()->objectName(); } if (!(eventPrefix() == "*" || eventPrefix() == ev || ev.startsWith(eventPrefix()+"."))) return false; @@ -682,10 +678,6 @@ void QScxml::beginSelectTransitions(QEvent* ev) } eventObj.setProperty("data",dataObj); emit eventTriggered(se->eventName()); - } else if (ev->type() == QEvent::Type(QEvent::User-2)) { - QString n = QString("done.state.")+((QStateFinishedEvent*)ev)->state()->objectName(); - eventObj.setProperty("name",qScriptValueFromValue<QString>(pvt->scriptEng, n)); - emit eventTriggered(n); } } scriptEngine()->globalObject().setProperty("_event",eventObj); @@ -851,13 +843,13 @@ QScxml::~QScxml() delete pvt; } -QScxmlInvoker::QString id () const +QString QScxmlInvoker::id () const { - return initEvent->invokeID; + return initEvent->metaData.invokeID; } void QScxmlInvoker::setID(const QString & id) { - initEvent->invokeID = id; + initEvent->metaData.invokeID = id; } QScxmlInvoker::~QScxmlInvoker() @@ -865,7 +857,7 @@ QScxmlInvoker::~QScxmlInvoker() if (cancelled) postParentEvent("CancelResponse"); else - postParentEvent(QString("done.invoke.%1").arg(metaData.invokeID)); + postParentEvent(QString("done.invoke.%1").arg(initEvent->metaData.invokeID)); } /*! \property QScxml::baseUrl @@ -914,7 +906,23 @@ struct ScTransitionInfo QStringList targets; QString anchor; QString script; - ScTransitionInfo() : {} + 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 @@ -945,13 +953,13 @@ struct ScExecContext QScxmlScriptExec* exec = new QScxmlScriptExec(script,sm); switch(type) { case StateEntry: - connect(state,SIGNAL(entered()),exec,SLOT(exec())); + QObject::connect(state,SIGNAL(entered()),exec,SLOT(exec())); break; case StateExit: - connect(state,SIGNAL(exited()),exec,SLOT(exec())); + QObject::connect(state,SIGNAL(exited()),exec,SLOT(exec())); break; case Transition: - connect(trans,SIGNAL(activated()),exec,SLOT(exec())); + QObject::connect(trans,SIGNAL(activated()),exec,SLOT(exec())); break; default: delete exec; @@ -971,7 +979,8 @@ class QScxmlLoader QList<ScHistoryInfo> historyInfo; QHash<QString,QAbstractState*> stateByID; QSet<QString> signalEvents; - void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename); + 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) @@ -1042,22 +1051,6 @@ class QScxmlAnchorRestore : public QObject } }; -class QScxmlScriptExec : public QObject -{ - Q_OBJECT - QScxml* scxml; - QString script; - public: - QScxmlScriptExec(const QString & scr, QScxml* sc) - :scxml(sc),script(scr) - { - } - public Q_SLOTS: - void execute() - { - scxml->executeScript(script); - } -}; static QString sanitize (const QString & str) { return str; @@ -1069,7 +1062,7 @@ static QString sanitize (const QStringRef & str) return sanitize(str.toString()); } -void QtScStreamLoader::loadState ( +void QScxmlLoader::loadState ( QState* stateParam, QIODevice *dev, const QString & stateID, @@ -1153,16 +1146,21 @@ void QtScStreamLoader::loadState ( } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) { if (curState) { QString id = r.attributes().value("id").toString(); - curHistoryState = curState->addHistoryState(r.attributes().value("type") == "shallow" ? QState::ShallowHistory : QState::DeepHistory); + 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(); - QtFinalState* f = new QtFinalState(curState); + 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) { @@ -1236,7 +1234,7 @@ void QtScStreamLoader::loadState ( idLocation = r.attributes().value("idlocation").toString(); if (idLocation.isEmpty()) idLocation = r.attributes().value("invokeid").toString(); - connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec())); + 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()) @@ -1270,7 +1268,7 @@ void QtScStreamLoader::loadState ( QString anc = r.attributes().value("anchor").toString(); if (!anc.isEmpty()) { stateMachine->pvt->anchorTransitions.insert(anc,curTransition); - connect (curTransition, SIGNAL(activated()),new QScxmlAnchorRestore(stateMachine,stateMachine->pvt,anc),SLOT(restore())); + QObject::connect (curTransition, SIGNAL(activated()),new QScxmlAnchorRestore(stateMachine,stateMachine->pvt,anc),SLOT(restore())); } inf.transition = curTransition; transitions.append(inf); @@ -1280,7 +1278,7 @@ void QtScStreamLoader::loadState ( 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) { - connect(curState,SIGNAL(exited()),new QScxmlAnchorSave(stateMachine,stateMachine->pvt,r.attributes().value("type").toString(),r.attributes().value("snapshot").toString(),curState),SLOT(save())); + 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(); @@ -1319,7 +1317,7 @@ void QtScStreamLoader::loadState ( } 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->parent(); + curExecContext.state = (curExecContext.state->parentState()); } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) { if (!idLocation.isEmpty()) curExecContext.script += idLocation + " = "; @@ -1384,6 +1382,10 @@ QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & file 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 @@ -1404,6 +1406,14 @@ QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & file 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. */ @@ -41,8 +41,10 @@ #ifndef QSCXML_H #define QSCXML_H -#include "qstatemachine.h" -#include "qabstracttransition.h" +#ifndef QT_NO_STATEMACHINE + +#include <QStateMachine> +#include <QAbstractTransition> #include <QVariant> #include <QEvent> #include <QStringList> @@ -102,7 +104,7 @@ class QScxmlTransition : public QAbstractTransition Q_SIGNALS: void activated (); protected: - bool eventTest(QEvent*) const; + bool eventTest(QEvent*); void onTransition (QEvent*) { emit activated(); } private: QScxml* scxml; @@ -115,7 +117,7 @@ class QScxmlInvoker : public QObject Q_PROPERTY (QString id READ id WRITE setID) protected: - QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* p) : QObject(p), initEvent(ievent) {} + QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* p) : QObject(p), initEvent(ievent),cancelled(false) {} public: virtual ~QScxmlInvoker(); @@ -133,6 +135,7 @@ class QScxmlInvoker : public QObject QScxml* parentStateMachine() { return (QScxml*)parent(); } void postParentEvent (QScxmlEvent* ev); QScxmlEvent* initEvent; + bool cancelled; friend struct QScxmlFunctions; }; @@ -184,6 +187,7 @@ class QScxml : public QStateMachine private Q_SLOTS: void registerSession(); void unregisterSession(); + void handleStateFinished(); Q_SIGNALS: void eventTriggered(const QString &); @@ -193,5 +197,5 @@ class QScxml : public QStateMachine friend class QScxmlLoader; friend struct QScxmlFunctions; }; - +#endif #endif // QSCXML_H diff --git a/src/qscxml.pri b/src/qscxml.pri new file mode 100644 index 0000000..effa64d --- /dev/null +++ b/src/qscxml.pri @@ -0,0 +1,6 @@ +QT += script +SOURCES += $$PWD/qscxml.cpp \ + $$PWD/qscxmlgui.cpp + +HEADERS += $$PWD/qscxml.h \ + $$PWD/qscxmlgui.h diff --git a/src/qscxmlgui.cpp b/src/qscxmlgui.cpp new file mode 100644 index 0000000..3fdfabc --- /dev/null +++ b/src/qscxmlgui.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** This file is part of a Qt Solutions component. +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Solutions Commercial License Agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and Nokia. +** +** 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. +** +** Please note Third Party Software included with Qt Solutions may impose +** additional restrictions and it is the user's responsibility to ensure +** that they have met the licensing requirements of the GPL, LGPL, or Qt +** Solutions Commercial license and the relevant license of the Third +** Party Software they are using. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +****************************************************************************/ +#include "qscxmlgui.h" +#include <QMenu> +#include <QDebug> +#include <QMessageBox> +#include <QScriptValueIterator> +#include <QScriptEngine> +#include <QSignalMapper> +/* + + { "parent" : parentObject, + "trackHovers" : true/false + "children": {{"type": "action", "text": "",}, + {"type": "menu"}, + {"type": "separator"} }, + */ +namespace +{ + void traverseMenu (QMenu* menu, QScriptValue value, QSignalMapper* clickMap, QSignalMapper* hoverMap, bool trackHover) + { + QScriptValueIterator it (value); + while (it.hasNext()) { + it.next(); + if (it.name() == "trackHover") { + trackHover = it.value().toBoolean(); + } else if (it.name() == "parent") { + } else if (it.name() == "children") { + QScriptValueIterator cit (it.value()); + while (cit.hasNext()) { + cit.next(); + QString type = cit.value().property("type").toString(); + if (type == "action") { + QAction* act = menu->addAction(""); + QScriptValueIterator ait (cit.value()); + while (ait.hasNext()) { + ait.next(); + if (ait.name() != "type") { + act->setProperty(ait.name().toAscii().constData(),ait.value().toVariant()); + } + } + QObject::connect(act,SIGNAL(triggered()),clickMap,SLOT(map())); + clickMap->setMapping(act,QString("menu.action." + cit.value().property("id").toString())); + if (trackHover) { + QObject::connect(act,SIGNAL(hovered()),hoverMap,SLOT(map())); + hoverMap->setMapping(act,QString("menu.hover." + cit.value().property("id").toString())); + } + } else if (type == "menu") { + traverseMenu(menu->addMenu(""),it.value(),clickMap,hoverMap,trackHover); + } else if (type == "separator") { + menu->addSeparator(); + } + } + } else { + menu->setProperty(it.name().toAscii().constData(),it.value().toVariant()); + } + } + } +}; + +void QScxmlMenuInvoker::activate () +{ + QScxmlEvent* ie = initEvent; + QScriptValue v = ie->content(); + QWidget* parent = qobject_cast<QWidget*>(v.property("parent").toQObject()); + menu = new QMenu(parent); + QSignalMapper* clickMap = new QSignalMapper(this); + QSignalMapper* hoverMap = new QSignalMapper(this); + connect (clickMap,SIGNAL(mapped(QString)), this, SLOT(postParentEvent(QString))); + connect (hoverMap,SIGNAL(mapped(QString)), this, SLOT(postParentEvent(QString))); + traverseMenu(menu,v,clickMap,hoverMap,false); + menu->setParent(parent,Qt::Widget); + menu->move(QPoint(0,0)); + menu->resize(parent->size()); + menu->show(); +} +void QScxmlMenuInvoker::cancel () +{ + if (menu) + menu->deleteLater(); + QScxmlInvoker::cancel(); +} + +Q_SCRIPT_DECLARE_QMETAOBJECT(QMenu,QWidget*) +Q_SCRIPT_DECLARE_QMETAOBJECT(QMessageBox,QWidget*) + void QScxmlMenuInvoker::initInvokerFactory(QScxml* sm) + { + QScriptEngine* se = sm->scriptEngine(); + se->globalObject().setProperty("QMenu",qScriptValueFromQMetaObject<QMenu>(se)); + } + void QScxmlMessageBoxInvoker::initInvokerFactory(QScxml* sm) + { + QScriptEngine* se = sm->scriptEngine(); + se->globalObject().setProperty("QMessageBox",qScriptValueFromQMetaObject<QMessageBox>(se)); + } + +void QScxmlMessageBoxInvoker::onFinished(int n) { + postParentEvent(new QScxmlEvent("q-messagebox.finished",QStringList()<<"result",QVariantList()<<QVariant(n))); +} +/* + { "parent": someWidget, "buttons": ...} + */ +void QScxmlMessageBoxInvoker::activate() +{ + QScriptValue v = initEvent->content(); + QWidget* parent = qobject_cast<QWidget*>(v.property("parent").toQObject()); + messageBox = new QMessageBox(parent); + messageBox->setModal(false); + QScriptValueIterator it (v); + while (it.hasNext()) { + it.next(); + if (it.name() == "standardButtons") { + messageBox->setStandardButtons((QMessageBox::StandardButtons)it.value().toInt32()); + } else if (it.name() == "icon") { + messageBox->setIcon((QMessageBox::Icon)it.value().toInt32()); + } else if (it.name() != "parent") { + messageBox->setProperty(it.name().toAscii().constData(),it.value().toVariant()); + } + } + connect(messageBox,SIGNAL(finished(int)),this,SLOT(onFinished(int))); + messageBox->show (); +} + +void QScxmlMessageBoxInvoker::cancel() +{ + messageBox->deleteLater(); + QScxmlInvoker::cancel(); +} diff --git a/src/qscxmlgui.h b/src/qscxmlgui.h new file mode 100644 index 0000000..2560ad7 --- /dev/null +++ b/src/qscxmlgui.h @@ -0,0 +1,48 @@ +#ifndef QSSMGUIINVOKERS_P_H +#define QSSMGUIINVOKERS_P_H + +#include "qscxml.h" +class QMenu; +class QMessageBox; + +class QScxmlMenuInvoker: public QScxmlInvoker +{ + Q_OBJECT + + public: + QScxmlMenuInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),menu(0) + { + } + static void initInvokerFactory(QScxml*); + static bool isTypeSupported (const QString & s) { return s== "q-menu"; } + public Q_SLOTS: + void activate (); + void cancel (); + + private: + QMenu* menu; +}; + + +class QScxmlMessageBoxInvoker: public QScxmlInvoker +{ + Q_OBJECT + + public: + QScxmlMessageBoxInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),messageBox(0) + { + } + + static void initInvokerFactory(QScxml*); + static bool isTypeSupported (const QString & s) { return s== "q-messagebox"; } + public Q_SLOTS: + void activate (); + void cancel (); + + private Q_SLOTS: + void onFinished (int); + private: + QMessageBox* messageBox; +}; + +#endif // QSSMGUIINVOKERS_P_H diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..df334f7 --- /dev/null +++ b/src/src.pro @@ -0,0 +1,10 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2009-05-24T14:33:48 +# +#------------------------------------------------- +QT += script +TARGET = qscxml +TEMPLATE = lib +CONFIG += staticlib +include (qscxml.pri)
\ No newline at end of file |