From d0441f605434a89b53735427e4e81182c65debbd Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Mon, 8 Jun 2009 12:27:03 -0700 Subject: scxml for 4.6 --- examples/blackjack/bj.qrc | 5 + examples/blackjack/blackjack.pro | 14 + examples/blackjack/blackjack.scxml | 305 +++++++ examples/blackjack/blackjack.ui | 174 ++++ examples/blackjack/main.cpp | 25 + examples/calc/calc.cpp | 35 + examples/calc/calc.h | 19 + examples/calc/calc.pro | 16 + examples/calc/calc.qrc | 5 + examples/calc/calc.scxml | 213 +++++ examples/calc/calc.ui | 187 +++++ examples/calc/main.cpp | 15 + examples/examples.pro | 2 + examples/mediaplayer/main.cpp | 58 ++ examples/mediaplayer/mediaplayer.pro | 23 + examples/mediaplayer/mediaplayer.qrc | 5 + examples/mediaplayer/mediaplayer.scxml | 240 ++++++ examples/mediaplayer/mediaplayer.ui | 145 ++++ examples/mediaplayer/songdata.h | 15 + examples/mediaplayer/spengine.cpp | 84 ++ examples/mediaplayer/spengine.h | 41 + examples/mediaplayer/spharvester.cpp | 68 ++ examples/mediaplayer/spharvester.h | 25 + examples/mediaplayer/spmodel.cpp | 274 ++++++ examples/mediaplayer/spmodel.h | 64 ++ examples/mediaplayer/spview.cpp | 97 +++ examples/mediaplayer/spview.h | 36 + qscxml.cpp | 1418 ------------------------------- qscxml.h | 197 ----- qscxml.pro | 14 +- src/qscxml.cpp | 1428 ++++++++++++++++++++++++++++++++ src/qscxml.h | 201 +++++ src/qscxml.pri | 6 + src/qscxmlgui.cpp | 171 ++++ src/qscxmlgui.h | 48 ++ src/src.pro | 10 + 36 files changed, 4056 insertions(+), 1627 deletions(-) create mode 100644 examples/blackjack/bj.qrc create mode 100644 examples/blackjack/blackjack.pro create mode 100644 examples/blackjack/blackjack.scxml create mode 100644 examples/blackjack/blackjack.ui create mode 100644 examples/blackjack/main.cpp create mode 100644 examples/calc/calc.cpp create mode 100644 examples/calc/calc.h create mode 100644 examples/calc/calc.pro create mode 100644 examples/calc/calc.qrc create mode 100644 examples/calc/calc.scxml create mode 100644 examples/calc/calc.ui create mode 100644 examples/calc/main.cpp create mode 100644 examples/examples.pro create mode 100644 examples/mediaplayer/main.cpp create mode 100644 examples/mediaplayer/mediaplayer.pro create mode 100644 examples/mediaplayer/mediaplayer.qrc create mode 100644 examples/mediaplayer/mediaplayer.scxml create mode 100644 examples/mediaplayer/mediaplayer.ui create mode 100644 examples/mediaplayer/songdata.h create mode 100644 examples/mediaplayer/spengine.cpp create mode 100644 examples/mediaplayer/spengine.h create mode 100644 examples/mediaplayer/spharvester.cpp create mode 100644 examples/mediaplayer/spharvester.h create mode 100644 examples/mediaplayer/spmodel.cpp create mode 100644 examples/mediaplayer/spmodel.h create mode 100644 examples/mediaplayer/spview.cpp create mode 100644 examples/mediaplayer/spview.h delete mode 100644 qscxml.cpp delete mode 100644 qscxml.h create mode 100644 src/qscxml.cpp create mode 100644 src/qscxml.h create mode 100644 src/qscxml.pri create mode 100644 src/qscxmlgui.cpp create mode 100644 src/qscxmlgui.h create mode 100644 src/src.pro 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 @@ + + + blackjack.scxml + + 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 @@ + + + + + + + + + + + [[welcomeLabel,"text","Welcome to Blackjack"]] + + + + + + + + + + + + + { + "parent" : gameWidget, + "icon" : QMessageBox.Question, + "windowTitle" : "Exit Blackjack", + "text" : "Are you sure?", + "standardButtons" : + QMessageBox.Yes|QMessageBox.No + } + + + + + + + + + + + + + + + + + [ + [betEdit,"enabled",true], + [betButton,"enabled",true], + [surrenderButton,"enabled",true], + [welcomeLabel,"text","Please place your bet"] + ] + + + + + + + + + + + + { + "parent" : betEdit, + "icon" : QMessageBox.Warning, + "windowTitle" : "Bet is Too High", + "text" : "Please Place Another Bet", + "standardButtons" : + QMessageBox.Ok + } + + + + + + + + + + + + + + + + + + + + + [ + [welcomeLabel,"text","Hit/Stand?"], + [hitButton,"enabled",true], + [standButton,"enabled",true] + ] + + + + + + + + + + + + + + + + + + + + + + [[welcomeLabel,"text","Game Over"]] + + + + + + + [[newRoundButton,"enabled",true]] + + + + + + + + + + + [[welcomeLabel,"text","You Won!"]] + + + [[welcomeLabel,"text","You Lost..."]] + + + + [[welcomeLabel,"text","You It's a draw."]] + + + + + + + + + + + + + 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 @@ + + + gameWidget + + + + 0 + 0 + 520 + 173 + + + + SCXML Blackjack Example + + + + + + + 6 + + + 9 + + + + + + 18 + + + + + + + + + + + + 16 + + + + color:#660033 + + + + + + + + + + 6 + + + 0 + + + + + Points: + + + + + + + color:#339999 + + + + + + + + + + false + + + 10 + + + false + + + + + + + false + + + Bet + + + + + + + + + 6 + + + 0 + + + + + true + + + New Game + + + + + + + false + + + New Round + + + + + + + false + + + Surrender + + + + + + + false + + + Hit + + + + + + + false + + + Stand + + + + + + + Exit + + + + + + + + + + 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 +#include +#include "ui_blackjack.h" +#include +#include +#include +#include +#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 +CalcWidget::CalcWidget(QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + QSignalMapper* mapper = new QSignalMapper(this); + connect (mapper, SIGNAL(mapped(QString)), this, SIGNAL(command(QString))); + QList buttons = findChildren(); + 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 +#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 @@ + + + calc.scxml + + 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 @@ + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + CalcClass + + + + 0 + 0 + 419 + 156 + + + + + 419 + 400 + + + + SCXML Calculator + + + * {background-color:black;color:white} +button {border-width:3; } + + + + + + +background-color: #CCCCCC; +color: black; +font: 12pt "Courier New"; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 6 + + + + + 4 + + + + + + + 1 + + + + + + + color:red;font-weight:bold + + + CE + + + + + + + 9 + + + + + + + 5 + + + + + + + 7 + + + + + + + 2 + + + + + + + 0 + + + + + + + 6 + + + + + + + 3 + + + + + + + . + + + + + + + + + + + + + + + - + + + + + + + * + + + + + + + / + + + + + + + = + + + + + + + 8 + + + + + + + color: red; font-weight:bold + + + C + + + + + + + + + + + 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 @@ + + + mediaplayer.scxml + + 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 @@ + + + + + [[listView,'styleSheet','QListView {font-size:20px}']] + + + + + + [[stackedWidget,"currentIndex",0]] + + + + + + + { "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; }() + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + [selectButton,"enabled",false], + [playingLabel,"text",model.currentSongTitle], + [midLabel,"text",model.currentSongArtist], + [posSlider,"minimum",0], + [posSlider,"maximum",engine.totalTime], + [stackedWidget,"currentIndex",1] + ] + + + + + + + + + + + + + + + + + + + + + + + + + [[stopButton,"enabled",false]] + + + + + [[stopButton,"enabled",true]] + + + + + + + [[playButton,"text","Pause"]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [[stopButton,"enabled",false], + [playButton,"enabled",false], + [prevButton,"enabled",false], + [nextButton,"enabled",false]] + + + + + + + + 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 @@ + + + mediaPlayerWidget + + + + 0 + 0 + 394 + 287 + + + + SCXML Media Player + + + + + + 0 + + + + + + + + + + + + + 0 + 50 + 291 + 61 + + + + QLabel {font-size: 24px; color: #336699} + + + TextLabel + + + + + + 0 + 120 + 291 + 21 + + + + TextLabel + + + + + + 0 + 180 + 301 + 16 + + + + 0 + + + Qt::Horizontal + + + + + + + + + + + + Home + + + + + + + Play + + + + + + + Prev + + + + + + + Next + + + + + + + Stop + + + + + + + Qt::Vertical + + + + + + + Select + + + + + + + Back + + + + + + + + + + + + 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 + +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 + +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 +#include +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 +#include +#include +#include +#include + +using namespace Phonon; + +struct SPHarvesterPvt +{ + MediaObject* mediaObject; + QQueue 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 +#include +#include +#include +// 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 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 +#include +#include +#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 +#include + +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 +#include +#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 diff --git a/qscxml.cpp b/qscxml.cpp deleted file mode 100644 index 741dc33..0000000 --- a/qscxml.cpp +++ /dev/null @@ -1,1418 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -** Contact: Qt Software Information (qt-info@nokia.com) -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the either Technology Preview License Agreement or the -** Beta Release License Agreement. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain -** additional rights. These rights are described in the Nokia Qt LGPL -** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this -** package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** If you are unsure which license is appropriate for your use, please -** contact the sales department at qt-sales@nokia.com. -** $QT_END_LICENSE$ -** -****************************************************************************/ -/*! - \class QScxml - \reentrant - - \brief The QScxml class provides a way to use scripting with the Qt State Machine Framework. - - \ingroup sctools - - Though can be used alone, QScxml is mainly a runtime helper to using the - state-machine framework with SCXML files. - - - \sa QStateMachine -*/ - -#include "qscxml.h" -#include -#include -#include "qstatefinishedevent.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "qhistorystate.h" -#include "qabstracttransition_p.h" -#include "qfinalstate.h" -#include "qabstractstate.h" -#ifdef QT_GUI_LIB -#include "qscxmlguiinvokers_p.h" -#endif - - -class QScxmlPrivate -{ - public: - - enum { MaxSnapshots = 200}; - - struct AnchorSnapshot - { - QAbstractState* state; - QString location; - QScriptValue snapshot; - QString anchorType; - }; - - - QScriptEngine* scriptEng; - QList invokerFactories; - QUrl burl; - QString sessionID; - QString startScript; - - - QStack snapshotStack; - QMultiHash anchorTransitions; - QHash curSnapshot; - - - static QHash sessions; -}; -QHash QScxmlPrivate::sessions; - -class QScxmlTimer : public QObject -{ - Q_OBJECT - public: - QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine),script(scr) - { - startTimer(delay); - } - protected: - void timerEvent(QTimerEvent*) - { - if (script.isFunction()) - script.call(); - else if (script.isString()) - script.engine()->evaluate(script.toString()); - } - - private: - QScriptValue script; - -}; - -static QScriptValue _q_deepCopy(const QScriptValue & val) -{ - if (val.isObject() || val.isArray()) { - QScriptValue v = val.isArray() ? val.engine()->newArray() : val.engine()->newObject(); - v.setData(val.data()); - QScriptValueIterator it (val); - while (it.hasNext()) { - it.next(); - v.setProperty(it.name(), _q_deepCopy(it.value())); - } - return v; - } else - return val; -} - - -struct QScxmlFunctions -{ -static QScriptValue cssTime(QScriptContext *context, QScriptEngine *engine) -{ - QString str; - if (context->argumentCount() > 0) - str = context->argument(0).toString(); - if (str == "") { - return qScriptValueFromValue(engine,0); - } - else if (str.endsWith("ms")) { - return qScriptValueFromValue(engine,(str.left(str.length()-2).toInt())); - } - else if (str.endsWith("s")) { - return qScriptValueFromValue(engine,(str.left(str.length()-1).toInt())*1000); - } - else { - return qScriptValueFromValue(engine, (str.toInt())); - } -} -static QScriptValue setTimeout(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() < 2) - return QScriptValue(); - int timeout = context->argument(1).toInt32(); - QScxmlTimer* tmr = new QScxmlTimer(engine,context->argument(0),timeout); - return engine->newQObject(tmr); -} -static QScriptValue script_print(QScriptContext *context, QScriptEngine *) -{ - if (context->argumentCount() > 0) - qDebug() << context->argument(0).toString(); - return QScriptValue(); -} -static QScriptValue clearTimeout(QScriptContext *context, QScriptEngine *) -{ - if (context->argumentCount() > 0) { - QObject* obj = context->argument(0).toQObject(); - obj->deleteLater(); - } - return QScriptValue(); -} - -static QScriptValue deepCopy(QScriptContext *context, QScriptEngine *) -{ - if (context->argumentCount() == 0) - return QScriptValue(); - else - return _q_deepCopy(context->argument(0)); -} - -static QScriptValue receiveSignal(QScriptContext *context, QScriptEngine *engine) -{ - QString eventName = context->thisObject().property("e").toString(); - if (!eventName.isEmpty()) { - QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); - if (scxml) { - QStringList pnames; - QVariantList pvals; - for (int i=0; i < context->argumentCount(); ++i) { - pnames << QString::number(i); - pvals << context->argument(i).toVariant(); - } - QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,QScriptValue()); - ev->metaData.kind = QScxmlEvent::MetaData::Platform; - scxml->postEvent(ev); - } - } - return QScriptValue(); -} - -static QScriptValue postEvent(QScriptContext *context, QScriptEngine *engine) -{ - QScxml* scxml = qobject_cast(engine->globalObject().property("scxml").toQObject()); - if (scxml) { - QString eventName,target,type; - QStringList pnames; - QVariantList pvals; - QScriptValue cnt; - if (context->argumentCount() > 0) - eventName = context->argument(0).toString(); - if (context->argumentCount() > 1) - target = context->argument(1).toString(); - if (context->argumentCount() > 2) - type = context->argument(2).toString(); - - if (!eventName.isEmpty() || !target.isEmpty()) { - if (context->argumentCount() > 3) - qScriptValueToSequence(context->argument(3),pnames); - if (context->argumentCount() > 4) { - QScriptValueIterator it (context->argument(4)); - while (it.hasNext()) { - it.next(); - pvals.append(it.value().toVariant()); - } - } if (context->argumentCount() > 5) - cnt = context->argument(5); - QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,cnt); - if (type == "scxml" || type == "") { - bool ok = true; - if (target == "_internal") { - ev->metaData.kind = QScxmlEvent::MetaData::Internal; - scxml->postInternalEvent(ev); - } else if (target == "scxml" || target == "") { - ev->metaData.kind = QScxmlEvent::MetaData::External; - scxml->postEvent(ev); - } else if (target == "_parent") { - QScxmlInvoker* p = qobject_cast(scxml->parent()); - if (p) - p->postParentEvent(ev); - else - ok = false; - } else { - QScxml* session = QScxmlPrivate::sessions[target]; - if (session) { - session->postEvent(ev); - } else - ok = false; - } - if (!ok) - scxml->postNamedEvent("error.targetunavailable"); - - } else { - scxml->postNamedEvent("error.send.typeinvalid"); - } - } - } - return QScriptValue(); -} - -// scxml.invoke (type, target, paramNames, paramValues, content) -static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine) -{ - QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); - if (scxml) { - QString type,target; - QStringList pnames; - QVariantList pvals; - QScriptValue cnt; - if (context->argumentCount() > 0) - type = context->argument(0).toString(); - if (type.isEmpty()) - type = "scxml"; - if (context->argumentCount() > 1) - target = context->argument(1).toString(); - if (context->argumentCount() > 2) - qScriptValueToSequence(context->argument(2),pnames); - if (context->argumentCount() > 3) { - QScriptValueIterator it (context->argument(3)); - while (it.hasNext()) { - it.next(); - pvals.append(it.value().toVariant()); - } - } if (context->argumentCount() > 4) - cnt = context->argument(4); - - - - QScxmlInvokerFactory* invf = NULL; - for (int i=0; i < scxml->pvt->invokerFactories.count() && invf == NULL; ++i) - if (scxml->pvt->invokerFactories[i]->isTypeSupported(type)) - invf = scxml->pvt->invokerFactories[i]; - if (invf) { - QScxmlEvent* ev = new QScxmlEvent("",pnames,pvals,cnt); - ev->metaData.origin = scxml->baseUrl(); - ev->metaData.target = target; - ev->metaData.targetType = type; - ev->metaData.originType = "scxml"; - ev->metaData.kind = QScxmlEvent::MetaData::External; - QScxmlInvoker* inv = invf->createInvoker(ev,scxml); - if (inv) - inv->activate(); - return engine->newQObject(inv); - } else { - scxml->postNamedEvent("error.invalidtargettype"); - } - - } - return QScriptValue(); -} - - -static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine) -{ - QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); - if (scxml) { - if (context->argumentCount() > 0) { - QString name = context->argument(0).toString(); - if (!name.isEmpty()) { - QSet cfg = scxml->configuration(); - foreach (QAbstractState* st, cfg) { - if (st->objectName() == name) - return qScriptValueFromValue(engine,true); - } - } - } - } - return qScriptValueFromValue(engine,false); - -} - -}; -/*! - \class QScxmlEvent - \brief The QScxmlEvent class stands for a general named event with a list of parameter names and parameter values. - - Encapsulates an event that conforms to the SCXML definition of events. - - \ingroup sctools - -*/ -/*! \enum QScxmlEvent::MetaData::Kind - - This enum specifies the kind (or context) of the event. - \value Platform An event coming from the itself, such as a script error. - \value Internal An event sent with a or . - \value External An event sent from an invoker, directly from C++, or from a element. -*/ - -/*! - Returns the name of the event. - */ - QString QScxmlEvent::eventName() const -{ - return ename; -} - /*! - Return a list containing the parameter names. - */ -QStringList QScxmlEvent::paramNames () const -{ - return pnames; -} - /*! - Return a list containing the parameter values. - */ -QVariantList QScxmlEvent::paramValues () const -{ - return pvals; -} - /*! - Return a QtScript object that can be passed as an additional parameter. - */ -QScriptValue QScxmlEvent::content () const -{ - return cnt; -} - /*! - Returns the parameter value equivalent to parameter \a name. - */ -QVariant QScxmlEvent::param (const QString & name) const -{ - int idx = pnames.indexOf(name); - if (idx >= 0) - return pvals[idx]; - else - return QVariant(); -} -/*! - Creates a QScxmlEvent named \a name, with parameter names \a paramNames, parameter values \a paramValues, and - a QtScript object \a content as an additional parameter. -*/ -QScxmlEvent::QScxmlEvent( - const QString & name, - const QStringList & paramNames, - const QVariantList & paramValues, - const QScriptValue & content) - - : QEvent(QScxmlEvent::eventType()),ename(name),pnames(paramNames),pvals(paramValues),cnt(content) -{ - metaData.kind = MetaData::Internal; -} - -/*! \class QScxmlTransition - \brief The QScxmlTransition class stands for a transition that responds to QScxmlEvent, and can be made conditional with a \l conditionExpression. - Equivalent to the SCXML transition tag. - - \ingroup sctools - */ -/*! \property QScxmlTransition::eventPrefix - The event prefix to be used when testing if the transition needs to be invoked. - Uses SCXML prefix matching. Use * to handle any event. - */ -/*! \property QScxmlTransition::conditionExpression - A QtScript expression that's evaluated to test whether the transition needs to be invoked. - */ - -/*! - Creates a new QScxmlTransition from \a state, that uses \a machine to evaluate the conditions. - */ -QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine) - : QAbstractTransition(state),scxml(machine) -{ -} - -/*! - \internal - */ -bool QScxmlTransition::eventTest(QEvent *e) const -{ - QScriptEngine* engine = scxml->scriptEngine(); - QString ev; - - 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; - } - - - if (!conditionExpression().isEmpty()) { - - QScriptValue v = engine->evaluate(conditionExpression(),scxml->baseUrl().toLocalFile()); - if (engine->hasUncaughtException()) { - - qDebug() << engine->uncaughtException().toString(); - QScxmlEvent* e = new QScxmlEvent("error.illegalcond", - QStringList()<< "error" << "expr" << "line" << "backtrace", - QVariantList() - << QVariant(engine->uncaughtException().toString()) - << QVariant(conditionExpression()) - << QVariant(engine->uncaughtExceptionLineNumber()) - << QVariant(engine->uncaughtExceptionBacktrace())); - e->metaData.kind = QScxmlEvent::MetaData::Platform; - scxml->postEvent(e); - engine->clearExceptions(); - return false; - } - return v.toBoolean(); - } - - return true; -} - -class QScxmlDefaultInvoker : public QScxmlInvoker -{ - Q_OBJECT - - - public: - QScxmlDefaultInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),cancelled(false),childSm(0) - { - childSm = QScxml::load (ievent->metaData.origin.resolved(ievent->metaData.target).toLocalFile(),this); - if (childSm == NULL) { - postParentEvent("error.targetunavailable"); - } else { - connect(childSm,SIGNAL(finished()),this,SLOT(deleteLater())); - - } - } - - - - static void initInvokerFactory(QScxml*) {} - - static bool isTypeSupported(const QString & t) { return t.isEmpty() || t.toLower() == "scxml"; } - - public Q_SLOTS: - void activate () - { - if (childSm) - childSm->start(); - } - - void cancel () - { - cancelled = true; - if (childSm) - childSm->stop(); - - } - - private: - bool cancelled; - QScxml* childSm; -}; -class QScxmlBindingInvoker : public QScxmlInvoker -{ - Q_OBJECT - QScriptValue content; - QScriptValue stored; - - public: - QScxmlBindingInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p) - { - } - - static void initInvokerFactory(QScxml*) {} - - static bool isTypeSupported(const QString & t) { return t.toLower() == "q-bindings"; } - - public Q_SLOTS: - void activate () - { - QScriptEngine* engine = ((QScxml*)parent())->scriptEngine(); - QScriptValue content = initEvent->content(); - if (content.isArray()) { - stored = content.engine()->newArray(content.property("length").toInt32()); - - QScriptValueIterator it (content); - for (int i=0; it.hasNext(); ++i) { - it.next(); - if (it.value().isArray()) { - QScriptValue object = it.value().property(0); - QString property = it.value().property(1).toString(); - QScriptValue val = it.value().property(2); - QScriptValue arr = engine->newArray(3); - arr.setProperty("0",it.value().property(0)); - arr.setProperty("1",it.value().property(1)); - if (object.isQObject()) { - QObject* o = object.toQObject(); - arr.setProperty("2",engine->newVariant(o->property(property.toAscii().constData()))); - o->setProperty(property.toAscii().constData(),val.toVariant()); - } else if (object.isObject()) { - arr.setProperty("2",object.property(property)); - object.setProperty(property,val); - } - stored.setProperty(i,arr); - } - } - } - } - - void cancel () - { - if (stored.isArray()) { - QScriptValueIterator it (stored); - while (it.hasNext()) { - it.next(); - if (it.value().isArray()) { - QScriptValue object = it.value().property(0); - QString property = it.value().property(1).toString(); - QScriptValue val = it.value().property(2); - if (object.isQObject()) { - QObject* o = object.toQObject(); - o->setProperty(property.toAscii().constData(),val.toVariant()); - } else if (object.isObject()) { - object.setProperty(property,val); - } - } - } - } - } -}; - -/*! -\fn QScxmlInvoker::~QScxmlInvoker() -*/ - - -/*! -\fn QScxml::eventTriggered(const QString & name) - -This signal is emitted when external event \a name is handled in the state machine. -*/ - -/*! - Creates a new QScxml object, with parent \a parent. - */ - -QScxml::QScxml(QObject* parent) - : QStateMachine(parent) -{ - pvt = new QScxmlPrivate; - pvt->scriptEng = new QScriptEngine(this); - QScriptValue glob = pvt->scriptEng->globalObject(); - QScriptValue utilObj = pvt->scriptEng->newObject(); - glob.setProperty("In",pvt->scriptEng->newFunction(QScxmlFunctions::isInState)); - glob.setProperty("_rcvSig",pvt->scriptEng->newFunction(QScxmlFunctions::receiveSignal)); - glob.setProperty("print",pvt->scriptEng->newFunction(QScxmlFunctions::script_print)); - utilObj.setProperty("postEvent",pvt->scriptEng->newFunction(QScxmlFunctions::postEvent)); - utilObj.setProperty("invoke",pvt->scriptEng->newFunction(QScxmlFunctions::invoke)); - utilObj.setProperty("cssTime",pvt->scriptEng->newFunction(QScxmlFunctions::cssTime)); - utilObj.setProperty("stateMachine",pvt->scriptEng->newQObject(this)); - utilObj.setProperty("clone",pvt->scriptEng->newFunction(QScxmlFunctions::deepCopy)); - utilObj.setProperty("setTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::setTimeout)); - utilObj.setProperty("clearTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::clearTimeout)); - QScriptValue dmObj = pvt->scriptEng->newObject(); - glob.setProperty("_data",pvt->scriptEng->newObject()); - glob.setProperty("_global",pvt->scriptEng->globalObject()); - glob.setProperty("scxml",utilObj); - glob.setProperty("connectSignalToEvent",pvt->scriptEng->evaluate("function(sig,ev) {sig.connect({'e':ev},_rcvSig);}")); - static QScxmlAutoInvokerFactory _s_defaultInvokerFactory; - static QScxmlAutoInvokerFactory _s_bindingInvokerFactory; - registerInvokerFactory(&_s_defaultInvokerFactory); - registerInvokerFactory(&_s_bindingInvokerFactory); - connect(this,SIGNAL(started()),this,SLOT(registerSession())); - connect(this,SIGNAL(stopped()),this,SLOT(unregisterSession())); -#ifdef QT_GUI_LIB - static QScxmlAutoInvokerFactory _s_msgboxInvokerFactory; - static QScxmlAutoInvokerFactory _s_menuInvokerFactory; - registerInvokerFactory(&_s_msgboxInvokerFactory); - registerInvokerFactory(&_s_menuInvokerFactory); -#endif -} - -/*! \internal */ -void QScxml::beginSelectTransitions(QEvent* ev) -{ - QScriptValue eventObj = pvt->scriptEng->newObject(); - if (ev) { - if (ev->type() == QScxmlEvent::eventType()) { - QScxmlEvent* se = (QScxmlEvent*)ev; - eventObj.setProperty("name",qScriptValueFromValue(pvt->scriptEng,se->eventName())); - eventObj.setProperty("target",qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue(se->metaData.target))); - eventObj.setProperty("targettype",qScriptValueFromValue(pvt->scriptEng,se->metaData.targetType)); - eventObj.setProperty("invokeid",qScriptValueFromValue(pvt->scriptEng,se->metaData.invokeID)); - eventObj.setProperty("origin",QScriptValue(qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue(se->metaData.origin)))); - eventObj.setProperty("originType",qScriptValueFromValue(pvt->scriptEng,se->metaData.originType)); - switch (se->metaData.kind) { - case QScxmlEvent::MetaData::Internal: - eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "internal")); - break; - case QScxmlEvent::MetaData::External: - eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "external")); - break; - case QScxmlEvent::MetaData::Platform: - eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "platform")); - default: - break; - - } - - QScriptValue dataObj = pvt->scriptEng->newObject(); - int i=0; - foreach (QString s, se->paramNames()) { - QScriptValue v = qScriptValueFromValue(pvt->scriptEng, se->paramValues()[i]); - dataObj.setProperty(QString::number(i),v); - dataObj.setProperty(s,v); - ++i; - } - eventObj.setProperty("data",dataObj); - emit eventTriggered(se->eventName()); - } else if (ev->type() == QEvent::Type(QEvent::User-2)) { - QString n = QString("done.state.")+((QStateFinishedEvent*)ev)->state()->objectName(); - eventObj.setProperty("name",qScriptValueFromValue(pvt->scriptEng, n)); - emit eventTriggered(n); - } - } - scriptEngine()->globalObject().setProperty("_event",eventObj); - - QHash curTargets; - - for (int i = pvt->snapshotStack.size()-1; i >= 0 && curTargets.size() < pvt->anchorTransitions.keys().size(); --i) { - if (!curTargets.contains(pvt->snapshotStack.at(i).anchorType)) { - curTargets[pvt->snapshotStack.at(i).anchorType] = pvt->snapshotStack.at(i).state; - } - } - for (QMultiHash::const_iterator it = pvt->anchorTransitions.constBegin(); it != pvt->anchorTransitions.constEnd(); ++it) { - it.value()->setTargetState(curTargets[it.key()]); - } - -} - -/*! \internal */ -void QScxml::endMicrostep(QEvent*) -{ - scriptEngine()->globalObject().setProperty("_event",QScriptValue()); - for (QHash::iterator - it = pvt->curSnapshot.begin(); - it != pvt->curSnapshot.end(); ++it) { - - pvt->snapshotStack.push(it.value()); - - } - if (pvt->snapshotStack.size() > QScxmlPrivate::MaxSnapshots) { - pvt->snapshotStack.remove(0,pvt->snapshotStack.size()-100); - } - pvt->curSnapshot.clear(); -} - -/*! Returns the script engine attached to the state-machine. */ -QScriptEngine* QScxml::scriptEngine () const -{ - return pvt->scriptEng; -} - -/*! - Registers object \a o to the script engine attached to the state machine. - The object can be accessible from global variable \a name. If \a name is not provided, - the object's name is used. If \a recursive is true, all the object's decendants are registered - as global objects, with their respective object names as variable names. -*/ -void QScxml::registerObject (QObject* o, const QString & name, bool recursive) -{ - QString n(name); - if (n.isEmpty()) - n = o->objectName(); - if (!n.isEmpty()) - pvt->scriptEng->globalObject().setProperty(n,pvt->scriptEng->newQObject(o)); - if (recursive) { - QObjectList ol = o->findChildren(); - foreach (QObject* oo, ol) { - if (!oo->objectName().isEmpty()) - registerObject(oo); - } - } -} - -/*! - Posts a QScxmlEvent named \a event, with no payload. - \sa QScxmlEvent - */ -void QScxml::postNamedEvent(const QString & event) -{ - QScxmlEvent* e = new QScxmlEvent(event); - e->metaData.kind = QScxmlEvent::MetaData::External; - postEvent(e); -} -/*! - Executes script \a s in the attached script engine. - If the script fails, a "error.illegalvalue" event is posted to the state machine. -*/ - -void QScxml::executeScript (const QString & s) -{ - pvt->scriptEng->evaluate (s,baseUrl().toLocalFile()); - if (pvt->scriptEng->hasUncaughtException()) { - QScxmlEvent* e = new QScxmlEvent("error.illegalvalue", - QStringList()<< "error" << "expr" << "line" << "backtrace", - QVariantList() - << QVariant(pvt->scriptEng->uncaughtException().toString()) - << QVariant(s) - << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber()) - << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace())); - e->metaData.kind = QScxmlEvent::MetaData::Platform; - postEvent(e); - pvt->scriptEng->clearExceptions(); - } -} - -/*! - Enabled invoker factory \a f to be called from tags. - */ - -void QScxml::registerInvokerFactory (QScxmlInvokerFactory* f) -{ - pvt->invokerFactories << f; - f->init(this); -} - -/*! \class QScxmlInvoker - \brief The QScxmlInvoker class an invoker, which the state-machine context can activate or cancel - with an tag. - - \ingroup sctools - - An invoker is a object that represents an external component that the state machine - can activate when the encompassing state is entered, or cancel when the encompassing - state is exited from. - */ - -/*! \fn QScxmlInvoker::QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* parent) - When reimplementing the constructor, always use the two parameters (\a ievent and \a parent), - as they're called from QScxmlInvokerFactory. -*/ - -/*! \fn QScxmlInvoker::activate() - This function is called when the encompassing state is entered. - The call to this function from the state-machine context is asynchronous, to make sure - that the state is not exited during the same step in which it's entered. - -*/ - -/*! \fn QScxmlInvoker::cancel() - Reimplement this function to allow for asynchronous cancellation of the invoker. - It's the invoker's responsibility to delete itself after this function has been called. - The default implementation deletes the invoker. -*/ - -/*! \fn QScxml* QScxmlInvoker::parentStateMachine() - Returns the state machine encompassing the invoker. - */ - -/*! - Posts an event \a e to the state machine encompassing the invoker. - */ -void QScxmlInvoker::postParentEvent (QScxmlEvent* e) -{ - e->metaData.origin = initEvent->metaData.target; - e->metaData.target = initEvent->metaData.origin; - e->metaData.originType = initEvent->metaData.targetType; - e->metaData.targetType = initEvent->metaData.originType; - e->metaData.kind = QScxmlEvent::MetaData::External; - e->metaData.invokeID = initEvent->metaData.invokeID; - parentStateMachine()->postEvent(e); -} -/*! \overload - Posts a QScxmlEvent named \a e to the encompassing state machine. - */ -void QScxmlInvoker::postParentEvent(const QString & e) -{ - QScxmlEvent* ev = new QScxmlEvent(e); - ev->metaData.kind = QScxmlEvent::MetaData::External; - postParentEvent(ev); -} -/*! \internal */ -QScxml::~QScxml() -{ - delete pvt; -} - -QScxmlInvoker::QString id () const -{ - return initEvent->invokeID; -} -void QScxmlInvoker::setID(const QString & id) -{ - initEvent->invokeID = id; -} - -QScxmlInvoker::~QScxmlInvoker() -{ - if (cancelled) - postParentEvent("CancelResponse"); - else - postParentEvent(QString("done.invoke.%1").arg(metaData.invokeID)); -} -/*! - \property QScxml::baseUrl - The url used to resolve scripts and invoke urls. -*/ -QUrl QScxml::baseUrl() const -{ - return pvt->burl; -} - -void QScxml::setBaseUrl(const QUrl & u) -{ - pvt->burl = u; -} - -void QScxml::registerSession() -{ - pvt->sessionID = QUuid::createUuid().toString(); - pvt->sessions[pvt->sessionID] = this; - pvt->scriptEng->globalObject().setProperty("_sessionid",qScriptValueFromValue(scriptEngine(), pvt->sessionID)); - executeScript(pvt->startScript); -} - -void QScxml::unregisterSession() -{ - pvt->scriptEng->globalObject().setProperty("_sessionid",QScriptValue()); - pvt->sessions.remove(pvt->sessionID); -} - -/*! - Returns a statically-generated event type to be used by SCXML events. -*/ -QEvent::Type QScxmlEvent::eventType() -{ - static QEvent::Type _t = (QEvent::Type)QEvent::registerEventType(QEvent::User+200); - return _t; -} -const char SCXML_NAMESPACE [] = "http://www.w3.org/2005/07/scxml"; - - - -struct ScTransitionInfo -{ - - QScxmlTransition* transition; - QStringList targets; - QString anchor; - QString script; - ScTransitionInfo() : {} -}; - -struct ScStateInfo -{ - QString initial; -}; - -struct ScHistoryInfo -{ - QHistoryState* hstate; - QString defaultStateID; -}; - -struct ScExecContext -{ - QScxml* sm; - QString script; - enum {None, StateEntry,StateExit,Transition } type; - QScxmlTransition* trans; - QAbstractState* state; - ScExecContext() : sm(NULL),type(None),trans(NULL),state(NULL) - { - } - - void applyScript() - { - if (!script.isEmpty()) { - QScxmlScriptExec* exec = new QScxmlScriptExec(script,sm); - switch(type) { - case StateEntry: - connect(state,SIGNAL(entered()),exec,SLOT(exec())); - break; - case StateExit: - connect(state,SIGNAL(exited()),exec,SLOT(exec())); - break; - case Transition: - connect(trans,SIGNAL(activated()),exec,SLOT(exec())); - break; - default: - delete exec; - break; - } - } - } -}; - -class QScxmlLoader -{ - public: - QScxml* stateMachine; - - QList transitions; - QHash stateInfo; - QList historyInfo; - QHash stateByID; - QSet signalEvents; - void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename); - QScxml* load (QIODevice* device, QObject* obj = NULL, const QString & filename = ""); - - QScriptValue evaluateFile (const QString & fn) - { - QFile f (fn); - f.open(QIODevice::ReadOnly); - return stateMachine->scriptEngine()->evaluate(QString::fromUtf8(f.readAll()),fn); - } -}; - -class QScxmlAnchorSave : public QObject -{ - Q_OBJECT - public: - QScxml* sm; - QScxmlPrivate* pvt; - QScxmlPrivate::AnchorSnapshot anchorSnapshot; - QScxmlAnchorSave(QScxml* p,QScxmlPrivate* pv, - const QString & type, const QString & loc, - QAbstractState* s) : - QObject(p),sm(p),pvt(pv) - { - anchorSnapshot.anchorType = type; - anchorSnapshot.location = loc; - anchorSnapshot.state = s; - } - - public Q_SLOTS: - - void save() - { - if (!anchorSnapshot.location.isEmpty()) { - anchorSnapshot.snapshot = _q_deepCopy(sm->scriptEngine()->evaluate(anchorSnapshot.location)); - } - pvt->curSnapshot[anchorSnapshot.anchorType] = anchorSnapshot; - } -}; - -class QScxmlAnchorRestore : public QObject -{ - Q_OBJECT - public: - QScxml* sm; - QScxmlPrivate* pvt; - QString anchorType; - QScxmlAnchorRestore(QScxml* p,QScxmlPrivate* pv, - const QString & type) : - QObject(p),sm(p),pvt(pv),anchorType(type) - { - - } - - public Q_SLOTS: - void restore () - { - pvt->curSnapshot.clear(); - while (!pvt->snapshotStack.isEmpty()) { - QScxmlPrivate::AnchorSnapshot s = pvt->snapshotStack.pop(); - if (s.anchorType == anchorType) { - if (s.location != "") { - sm->scriptEngine()->globalObject().setProperty("_snapshot",s.snapshot); - sm->scriptEngine()->evaluate(QString ("%1 = _snapshot;").arg(s.location)); - sm->scriptEngine()->globalObject().setProperty("_snapshot",QScriptValue()); - } - break; - } - } - } -}; - -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; -// return QString("eval(unescape(\"%1\"))"). -// arg(QString::fromAscii(str.trimmed().toUtf8().toPercentEncoding(QByteArray("[]()<>;:#/'`_-., \t@!^&*{}")))); -} -static QString sanitize (const QStringRef & str) -{ - return sanitize(str.toString()); -} - -void QtScStreamLoader::loadState ( - QState* stateParam, - QIODevice *dev, - const QString & stateID, - const QString & filename) -{ - QXmlStreamReader r (dev); - QState* curState = NULL; - ScExecContext curExecContext; - curExecContext.sm = stateMachine; - QState* topLevelState = NULL; - QHistoryState* curHistoryState = NULL; - QString initialID = ""; - QString idLocation; - QScxmlTransition* curTransition = NULL; - bool inRoot = true; - while (!r.atEnd()) { - r.readNext(); - if (r.hasError()) { - qDebug() << QString("SCXML read error at line %1, column %2: %3").arg(r.lineNumber()).arg(r. columnNumber()).arg(r.errorString()); - return; - } - if (r.namespaceUri() == SCXML_NAMESPACE || r.namespaceUri() == "") { - if (r.isStartElement()) { - if (r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) { - if (stateID == "") { - topLevelState = curState = stateParam; - stateInfo[curState].initial = r.attributes().value("initial").toString(); - if (curState == stateMachine->rootState()) { - stateMachine->scriptEngine()->globalObject().setProperty("_name",qScriptValueFromValue(stateMachine->scriptEngine(),r.attributes().value("name").toString())); - } - - } - } else if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) { - inRoot = false; - QString id = r.attributes().value("id").toString(); - QState* newState = NULL; - if (curState) { - newState= new QState(r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0 ? QState::ParallelStates : QState::ExclusiveStates, - curState); - } else if (id == stateID) { - topLevelState = newState = stateParam; - - } - if (newState) { - stateInfo[newState].initial = r.attributes().value("initial").toString(); - newState->setObjectName(id); - if (!id.isEmpty() && stateInfo[curState].initial == id) { - - if (curState == stateMachine->rootState()) - stateMachine->setInitialState(newState); - else - curState->setInitialState(newState); - } - QString src = r.attributes().value("src").toString(); - if (!src.isEmpty()) { - int refidx = src.indexOf('#'); - QString srcfile, refid; - if (refidx > 0) { - srcfile = src.left(refidx); - refid = src.mid(refidx+1); - } else - srcfile = src; - srcfile = QDir::cleanPath( QFileInfo(filename).dir().absoluteFilePath(srcfile)); - QFile newFile (srcfile); - if (newFile.exists()) { - newFile.open(QIODevice::ReadOnly); - loadState(newState,&newFile,refid,srcfile); - } - } - initialID = r.attributes().value("initial").toString(); - stateByID[id] = newState; - curState = newState; - curExecContext.state = newState; - } - - } else if (r.name().toString().compare("initial",Qt::CaseInsensitive) == 0) { - if (curState && stateInfo[curState].initial == "") { - QState* newState = new QState(curState); - curState->setInitialState(newState); - } - } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) { - if (curState) { - QString id = r.attributes().value("id").toString(); - curHistoryState = curState->addHistoryState(r.attributes().value("type") == "shallow" ? QState::ShallowHistory : QState::DeepHistory); - 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); - f->setObjectName(id); - curExecContext.state = f; - stateByID[id] = f; - } - } else if (r.name().toString().compare("script",Qt::CaseInsensitive) == 0) { - QString txt = r.readElementText().trimmed(); - if (curExecContext.type == ScExecContext::None && curState == topLevelState) { - stateMachine->executeScript(txt); - } else - curExecContext.script += txt; - } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) { - curExecContext.script += - QString("print('[' + %1 + '][' + %2 + ']' + %3);") - .arg(sanitize(r.attributes().value("label"))) - .arg(sanitize(r.attributes().value("level"))) - .arg(sanitize(r.attributes().value("expr"))); - - } else if (r.name().toString().compare("assign",Qt::CaseInsensitive) == 0) { - QString locattr = r.attributes().value("location").toString(); - if (locattr.isEmpty()) { - locattr = r.attributes().value("dataid").toString(); - if (!locattr.isEmpty()) - locattr = "_data." + locattr; - } - if (!locattr.isEmpty()) { - curExecContext.script += QString ("%1 = %2;").arg(locattr).arg(sanitize(r.attributes().value("expr"))); - } - } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) { - curExecContext.script += QString("if (%1) {").arg(sanitize(r.attributes().value("cond"))); - } else if (r.name().toString().compare("elseif",Qt::CaseInsensitive) == 0) { - curExecContext.script += QString("} elseif (%1) {").arg(sanitize(r.attributes().value("cond"))); - } else if (r.name().toString().compare("else",Qt::CaseInsensitive) == 0) { - curExecContext.script += " } else { "; - } else if (r.name().toString().compare("cancel",Qt::CaseInsensitive) == 0) { - curExecContext.script += QString("scxml.clearTimeout (%1);").arg(sanitize(r.attributes().value("id"))); - } else if (r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0) { - curExecContext.type = ScExecContext::StateEntry; - curExecContext.script = ""; - } else if (r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0) { - curExecContext.type = ScExecContext::StateExit; - curExecContext.script = ""; - } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0 || r.name().toString().compare("event",Qt::CaseInsensitive) == 0 ) { - QString ev = r.attributes().value("event").toString(); - if (ev.isEmpty()) - ev = r.attributes().value("name").toString(); - curExecContext.script += - QString("{" - "var paramNames = []; var paramValues = []; " - "var content = ''; var eventName='%1'; " - "var target = '_internal'; var targetType = 'scxml'; ").arg(ev); - - } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) { - QString type = r.attributes().value("type").toString(); - if (type.isEmpty()) - type = r.attributes().value("targettype").toString(); - curExecContext.script += - QString("{" - "var paramNames = [%1]; var paramValues = []; " - "var content = ''; var eventName=%2; " - "var targetType = %3; var target = %4;") - .arg(r.attributes().value("namelist").toString().replace(" ",",")) - .arg(sanitize(r.attributes().value("event").toString())) - .arg(type.isEmpty() ? "'scxml'" : sanitize(r.attributes().value("type"))) - .arg(r.attributes().value("target").length() ? sanitize(r.attributes().value("target")) : "''"); - idLocation = r.attributes().value("idlocation").toString(); - if (idLocation.isEmpty()) - idLocation = r.attributes().value("sendid").toString(); - - curExecContext.script += QString("var delay = %1; ").arg(r.attributes().value("delay").length() - ? QString("scxml.cssTime(%1)").arg(sanitize(r.attributes().value("delay"))) - : "0"); - } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) { - idLocation = r.attributes().value("idlocation").toString(); - if (idLocation.isEmpty()) - idLocation = r.attributes().value("invokeid").toString(); - connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec())); - - QString type = r.attributes().value("type").toString(); - if (type.isEmpty()) - type = r.attributes().value("targettype").toString(); - curExecContext.type = ScExecContext::StateEntry; - curExecContext.state = curState; - curExecContext.script = - QString("{" - "var paramNames = []; var paramValues = []; " - "var content = ''; " - "var srcType = \"%1\"; var src = %2;") - .arg(type.length() ? type : "scxml") - .arg(r.attributes().value("src").length() ? sanitize(r.attributes().value("target")) : "\"\""); - - - } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) { - if (curHistoryState) { - ScHistoryInfo inf; - inf.hstate = curHistoryState; - inf.defaultStateID = r.attributes().value("target").toString(); - historyInfo.append(inf); - } else { - ScTransitionInfo inf; - inf.targets = r.attributes().value("target").toString().split(' '); - curExecContext.type = ScExecContext::Transition; - curExecContext.script = ""; - curTransition = new QScxmlTransition(curState,stateMachine); - curTransition->setConditionExpression(r.attributes().value("cond").toString()); - curTransition->setEventPrefix(r.attributes().value("event").toString()); - curExecContext.trans = curTransition; - QString anc = r.attributes().value("anchor").toString(); - if (!anc.isEmpty()) { - stateMachine->pvt->anchorTransitions.insert(anc,curTransition); - connect (curTransition, SIGNAL(activated()),new QScxmlAnchorRestore(stateMachine,stateMachine->pvt,anc),SLOT(restore())); - } - inf.transition = curTransition; - transitions.append(inf); - if (curTransition->eventPrefix().startsWith("q-signal:")) { - signalEvents.insert(curTransition->eventPrefix()); - } - curTransition->setObjectName(QString ("%1 to %2 on %3 if %4 (anchor=%5)").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefix()).arg(curTransition->conditionExpression()).arg(anc)); - } - } else if (r.name().toString().compare("anchor",Qt::CaseInsensitive) == 0) { - 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(stateMachine->scriptEngine(),"") ; - QString id = r.attributes().value("id").toString(); - if (r.attributes().value("src").length()) - val = evaluateFile(QFileInfo(filename).dir().absoluteFilePath(r.attributes().value("src").toString())); - else { - if (r.attributes().value("expr").length()) { - val = stateMachine->scriptEngine()->evaluate(r.attributes().value("expr").toString()); - } else { - QString t = r.readElementText(); - if (!t.isEmpty()) - val = stateMachine->scriptEngine()->evaluate(t); - } - } - stateMachine->scriptEngine()->evaluate("_data") - .setProperty(id,val); - } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) { - curExecContext.script += - QString("paramNames[paramNames.length] = \"%1\";") - .arg(r.attributes().value("name").toString()); - curExecContext.script += - QString("paramValues[paramValues.length] = %1;") - .arg(sanitize(r.attributes().value("expr"))); - - } else if (r.name().toString().compare("content",Qt::CaseInsensitive) == 0) { - curExecContext.script += QString("content = %1; ").arg(sanitize(r.readElementText())); - } - } else if (r.isEndElement()) { - if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) { - if (curState == topLevelState) { - return; - } else { - curState = qobject_cast(curState->parent()); - curExecContext.state = curState; - } - } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) { - curHistoryState = NULL; - } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) { - curExecContext.state = curExecContext.state->parent(); - } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) { - if (!idLocation.isEmpty()) - curExecContext.script += idLocation + " = "; - curExecContext.script += QString("scxml.setTimeout(function() { " - "scxml.postEvent(" - "eventName,target,targetType,paramNames,paramValues,content" - ");" - "}, delay); }"); - idLocation = ""; - } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0) { - curExecContext.script += "scxml.postEvent(eventName,target,targetType,paramNames,paramValues,content); }"; - } else if ( - r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0 - || r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0 - || r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) { - curExecContext.state = curState; - curExecContext.type = r.name().toString().compare("onexit",Qt::CaseInsensitive)==0 ? ScExecContext::StateExit : ScExecContext::StateEntry; - curExecContext.applyScript(); - curExecContext.type = ScExecContext::None; - } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) { - if (!curHistoryState) { - curExecContext.trans = curTransition; - curExecContext.type = ScExecContext::Transition; - curExecContext.applyScript(); - } - - ScTransitionInfo* ti = &(transitions.last()); - if (!curExecContext.script.isEmpty() && ti->anchor != "") - ti->script = curExecContext.script; - curExecContext.type = ScExecContext::None; - } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) { - curExecContext.script += QString("invoke_%1 = scxml.invoke(srcType,src,paramNames,paramValues,content); }").arg(curState->objectName()); - if (!idLocation.isEmpty()) { - curExecContext.script += QString("%1 = invoke_%2;").arg(idLocation).arg(curState->objectName()); - } - curExecContext.state = curState; - curExecContext.type = ScExecContext::StateEntry; - curExecContext.applyScript(); - idLocation = ""; - curExecContext.type = ScExecContext::None; - } - } - } - } -} - - -QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename) -{ - stateMachine = new QScxml(obj); - // traverse through the states - loadState(stateMachine->rootState(),device,"",filename); - - // resolve history default state - foreach (ScHistoryInfo h, historyInfo) { - h.hstate->setDefaultState(stateByID[h.defaultStateID]); - } - foreach (QString s, signalEvents) { - QString sig = s; - sig = sig.mid(sig.indexOf(':')+1); - sig = sig.left(sig.indexOf('(')); - QString scr = QString("%1.connect({e:\"%2\"},_rcvSig);\n").arg(sig).arg(s); - stateMachine->pvt->startScript += scr; - } - - // resolve transitions - - foreach (ScTransitionInfo t, transitions) { - QList states; - if (!t.targets.isEmpty()) { - foreach (QString s, t.targets) { - if (!s.trimmed().isEmpty()) { - QAbstractState* st = stateByID[s]; - if (st) - states.append(st); - } - } - t.transition->setTargetStates(states); - } - } - - return stateMachine; -} - -/*! - Loads a state machine from an scxml file located at \a filename, with parent object \a o. - */ -QScxml* QScxml::load (const QString & filename, QObject* o) -{ - QScxmlLoader l; - QFile f (filename); - f.open(QIODevice::ReadOnly); - return l.load(&f,o,filename); -} - -#include "qscxml.moc" diff --git a/qscxml.h b/qscxml.h deleted file mode 100644 index 896b247..0000000 --- a/qscxml.h +++ /dev/null @@ -1,197 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -** Contact: Qt Software Information (qt-info@nokia.com) -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the either Technology Preview License Agreement or the -** Beta Release License Agreement. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain -** additional rights. These rights are described in the Nokia Qt LGPL -** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this -** package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** If you are unsure which license is appropriate for your use, please -** contact the sales department at qt-sales@nokia.com. -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QSCXML_H -#define QSCXML_H -#include "qstatemachine.h" -#include "qabstracttransition.h" -#include -#include -#include -#include -#include - -class QScriptEngine; -class QScxml; - -class QScxmlEvent : public QEvent -{ - public: - static QEvent::Type eventType(); - QString eventName() const; - QStringList paramNames () const; - QVariantList paramValues () const; - QScriptValue content () const; - QVariant param (const QString & name) const; - QScxmlEvent ( - const QString & name, - const QStringList & paramNames = QStringList(), - const QVariantList & paramValues = QVariantList(), - const QScriptValue & content = QScriptValue()); - - struct MetaData - { - QUrl origin,target; - QString originType, targetType; - QString invokeID; - enum Kind { Platform, Internal, External } kind; - }; - - MetaData metaData; - - private: - QString ename; - QStringList pnames; - QVariantList pvals; - QScriptValue cnt; -}; - - -class QScxmlTransition : public QAbstractTransition -{ - Q_OBJECT - Q_PROPERTY(QString conditionExpression READ conditionExpression WRITE setConditionExpression) - Q_PROPERTY(QString eventPrefix READ eventPrefix WRITE setEventPrefix) - - public: - QScxmlTransition (QState* state, QScxml* machine); - - QString conditionExpression () const { return cond; } - void setConditionExpression (const QString & c) { cond = c; } - QString eventPrefix () const { return ev; } - void setEventPrefix (const QString & e) { ev = e; } - - Q_SIGNALS: - void activated (); - protected: - bool eventTest(QEvent*) const; - void onTransition (QEvent*) { emit activated(); } - private: - QScxml* scxml; - QString ev,cond; -}; - -class QScxmlInvoker : public QObject -{ - Q_OBJECT - Q_PROPERTY (QString id READ id WRITE setID) - - protected: - QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* p) : QObject(p), initEvent(ievent) {} - - public: - virtual ~QScxmlInvoker(); - QString id () const; - void setID(const QString &); - - public Q_SLOTS: - virtual void activate() = 0; - virtual void cancel() { deleteLater(); } - - protected Q_SLOTS: - void postParentEvent (const QString & event); - - protected: - QScxml* parentStateMachine() { return (QScxml*)parent(); } - void postParentEvent (QScxmlEvent* ev); - QScxmlEvent* initEvent; - - friend struct QScxmlFunctions; -}; - -struct QScxmlInvokerFactory -{ - virtual QScxmlInvoker* createInvoker (QScxmlEvent* event, QScxml* stateMachine) = 0; - virtual bool isTypeSupported (const QString & type) const = 0; - virtual void init (QScxml*) = 0; -}; - -template -class QScxmlAutoInvokerFactory : public QScxmlInvokerFactory -{ - QScxmlInvoker* createInvoker (QScxmlEvent* _e, QScxml* _sm) { return new T(_e,_sm); } - bool isTypeSupported(const QString & _s) const { return T::isTypeSupported(_s); } - void init (QScxml* sm) { T::initInvokerFactory(sm); } -}; - - -class QScxml : public QStateMachine -{ - Q_OBJECT - - Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl) - - - public: - QScxml(QObject* o = NULL); - virtual ~QScxml(); - protected: - // overloaded to store the event for the script environment's use (_event), and to convert - // StateFinished events to "done." named events - virtual void beginSelectTransitions(QEvent*); - virtual void endMicrostep(QEvent*); - - public: - QScriptEngine* scriptEngine () const; - void registerObject (QObject* object, const QString & name = QString(), bool recursive = false); - void registerInvokerFactory (QScxmlInvokerFactory* f); - void setBaseUrl (const QUrl &); - QUrl baseUrl () const; - static QScxml* load (const QString & filename, QObject* o = NULL); - - public Q_SLOTS: - void postNamedEvent(const QString &); - void executeScript (const QString &); - - private Q_SLOTS: - void registerSession(); - void unregisterSession(); - - Q_SIGNALS: - void eventTriggered(const QString &); - - private: - class QScxmlPrivate* pvt; - friend class QScxmlLoader; - friend struct QScxmlFunctions; -}; - -#endif // QSCXML_H diff --git a/qscxml.pro b/qscxml.pro index 208c38e..67fed9e 100644 --- a/qscxml.pro +++ b/qscxml.pro @@ -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/src/qscxml.cpp b/src/qscxml.cpp new file mode 100644 index 0000000..8190056 --- /dev/null +++ b/src/qscxml.cpp @@ -0,0 +1,1428 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! + \class QScxml + \reentrant + + \brief The QScxml class provides a way to use scripting with the Qt State Machine Framework. + + \ingroup sctools + + Though can be used alone, QScxml is mainly a runtime helper to using the + state-machine framework with SCXML files. + + + \sa QStateMachine +*/ + +#include "qscxml.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_GUI_LIB +#include "qscxmlgui.h" +#endif + + +class QScxmlPrivate +{ + public: + + enum { MaxSnapshots = 200}; + + struct AnchorSnapshot + { + QAbstractState* state; + QString location; + QScriptValue snapshot; + QString anchorType; + }; + + + QScriptEngine* scriptEng; + QList invokerFactories; + QUrl burl; + QString sessionID; + QString startScript; + + + QStack snapshotStack; + QMultiHash anchorTransitions; + QHash curSnapshot; + + + static QHash sessions; +}; +QHash QScxmlPrivate::sessions; + +class QScxmlTimer : public QObject +{ + Q_OBJECT + public: + QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine),script(scr) + { + startTimer(delay); + } + protected: + void timerEvent(QTimerEvent*) + { + if (script.isFunction()) + script.call(); + else if (script.isString()) + script.engine()->evaluate(script.toString()); + } + + private: + QScriptValue script; + +}; + +static QScriptValue _q_deepCopy(const QScriptValue & val) +{ + if (val.isObject() || val.isArray()) { + QScriptValue v = val.isArray() ? val.engine()->newArray() : val.engine()->newObject(); + v.setData(val.data()); + QScriptValueIterator it (val); + while (it.hasNext()) { + it.next(); + v.setProperty(it.name(), _q_deepCopy(it.value())); + } + return v; + } else + return val; +} + + +struct QScxmlFunctions +{ +static QScriptValue cssTime(QScriptContext *context, QScriptEngine *engine) +{ + QString str; + if (context->argumentCount() > 0) + str = context->argument(0).toString(); + if (str == "") { + return qScriptValueFromValue(engine,0); + } + else if (str.endsWith("ms")) { + return qScriptValueFromValue(engine,(str.left(str.length()-2).toInt())); + } + else if (str.endsWith("s")) { + return qScriptValueFromValue(engine,(str.left(str.length()-1).toInt())*1000); + } + else { + return qScriptValueFromValue(engine, (str.toInt())); + } +} +static QScriptValue setTimeout(QScriptContext *context, QScriptEngine *engine) +{ + if (context->argumentCount() < 2) + return QScriptValue(); + int timeout = context->argument(1).toInt32(); + QScxmlTimer* tmr = new QScxmlTimer(engine,context->argument(0),timeout); + return engine->newQObject(tmr); +} +static QScriptValue script_print(QScriptContext *context, QScriptEngine *) +{ + if (context->argumentCount() > 0) + qDebug() << context->argument(0).toString(); + return QScriptValue(); +} +static QScriptValue clearTimeout(QScriptContext *context, QScriptEngine *) +{ + if (context->argumentCount() > 0) { + QObject* obj = context->argument(0).toQObject(); + obj->deleteLater(); + } + return QScriptValue(); +} + +static QScriptValue deepCopy(QScriptContext *context, QScriptEngine *) +{ + if (context->argumentCount() == 0) + return QScriptValue(); + else + return _q_deepCopy(context->argument(0)); +} + +static QScriptValue receiveSignal(QScriptContext *context, QScriptEngine *engine) +{ + QString eventName = context->thisObject().property("e").toString(); + if (!eventName.isEmpty()) { + QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); + if (scxml) { + QStringList pnames; + QVariantList pvals; + for (int i=0; i < context->argumentCount(); ++i) { + pnames << QString::number(i); + pvals << context->argument(i).toVariant(); + } + QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,QScriptValue()); + ev->metaData.kind = QScxmlEvent::MetaData::Platform; + scxml->postEvent(ev); + } + } + return QScriptValue(); +} + +static QScriptValue postEvent(QScriptContext *context, QScriptEngine *engine) +{ + QScxml* scxml = qobject_cast(engine->globalObject().property("scxml").toQObject()); + if (scxml) { + QString eventName,target,type; + QStringList pnames; + QVariantList pvals; + QScriptValue cnt; + if (context->argumentCount() > 0) + eventName = context->argument(0).toString(); + if (context->argumentCount() > 1) + target = context->argument(1).toString(); + if (context->argumentCount() > 2) + type = context->argument(2).toString(); + + if (!eventName.isEmpty() || !target.isEmpty()) { + if (context->argumentCount() > 3) + qScriptValueToSequence(context->argument(3),pnames); + if (context->argumentCount() > 4) { + QScriptValueIterator it (context->argument(4)); + while (it.hasNext()) { + it.next(); + pvals.append(it.value().toVariant()); + } + } if (context->argumentCount() > 5) + cnt = context->argument(5); + QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,cnt); + if (type == "scxml" || type == "") { + bool ok = true; + if (target == "_internal") { + ev->metaData.kind = QScxmlEvent::MetaData::Internal; + scxml->postInternalEvent(ev); + } else if (target == "scxml" || target == "") { + ev->metaData.kind = QScxmlEvent::MetaData::External; + scxml->postEvent(ev); + } else if (target == "_parent") { + QScxmlInvoker* p = qobject_cast(scxml->parent()); + if (p) + p->postParentEvent(ev); + else + ok = false; + } else { + QScxml* session = QScxmlPrivate::sessions[target]; + if (session) { + session->postEvent(ev); + } else + ok = false; + } + if (!ok) + scxml->postNamedEvent("error.targetunavailable"); + + } else { + scxml->postNamedEvent("error.send.typeinvalid"); + } + } + } + return QScriptValue(); +} + +// scxml.invoke (type, target, paramNames, paramValues, content) +static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine) +{ + QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); + if (scxml) { + QString type,target; + QStringList pnames; + QVariantList pvals; + QScriptValue cnt; + if (context->argumentCount() > 0) + type = context->argument(0).toString(); + if (type.isEmpty()) + type = "scxml"; + if (context->argumentCount() > 1) + target = context->argument(1).toString(); + if (context->argumentCount() > 2) + qScriptValueToSequence(context->argument(2),pnames); + if (context->argumentCount() > 3) { + QScriptValueIterator it (context->argument(3)); + while (it.hasNext()) { + it.next(); + pvals.append(it.value().toVariant()); + } + } if (context->argumentCount() > 4) + cnt = context->argument(4); + + + + QScxmlInvokerFactory* invf = NULL; + for (int i=0; i < scxml->pvt->invokerFactories.count() && invf == NULL; ++i) + if (scxml->pvt->invokerFactories[i]->isTypeSupported(type)) + invf = scxml->pvt->invokerFactories[i]; + if (invf) { + QScxmlEvent* ev = new QScxmlEvent("",pnames,pvals,cnt); + ev->metaData.origin = scxml->baseUrl(); + ev->metaData.target = target; + ev->metaData.targetType = type; + ev->metaData.originType = "scxml"; + ev->metaData.kind = QScxmlEvent::MetaData::External; + QScxmlInvoker* inv = invf->createInvoker(ev,scxml); + if (inv) + inv->activate(); + return engine->newQObject(inv); + } else { + scxml->postNamedEvent("error.invalidtargettype"); + } + + } + return QScriptValue(); +} + + +static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine) +{ + QScxml* scxml = qobject_cast(engine->globalObject().property("scxml.stateMachine").toQObject()); + if (scxml) { + if (context->argumentCount() > 0) { + QString name = context->argument(0).toString(); + if (!name.isEmpty()) { + QSet cfg = scxml->configuration(); + foreach (QAbstractState* st, cfg) { + if (st->objectName() == name) + return qScriptValueFromValue(engine,true); + } + } + } + } + return qScriptValueFromValue(engine,false); + +} + +}; +/*! + \class QScxmlEvent + \brief The QScxmlEvent class stands for a general named event with a list of parameter names and parameter values. + + Encapsulates an event that conforms to the SCXML definition of events. + + \ingroup sctools + +*/ +/*! \enum QScxmlEvent::MetaData::Kind + + This enum specifies the kind (or context) of the event. + \value Platform An event coming from the itself, such as a script error. + \value Internal An event sent with a or . + \value External An event sent from an invoker, directly from C++, or from a element. +*/ + +/*! + Returns the name of the event. + */ + QString QScxmlEvent::eventName() const +{ + return ename; +} + /*! + Return a list containing the parameter names. + */ +QStringList QScxmlEvent::paramNames () const +{ + return pnames; +} + /*! + Return a list containing the parameter values. + */ +QVariantList QScxmlEvent::paramValues () const +{ + return pvals; +} + /*! + Return a QtScript object that can be passed as an additional parameter. + */ +QScriptValue QScxmlEvent::content () const +{ + return cnt; +} + /*! + Returns the parameter value equivalent to parameter \a name. + */ +QVariant QScxmlEvent::param (const QString & name) const +{ + int idx = pnames.indexOf(name); + if (idx >= 0) + return pvals[idx]; + else + return QVariant(); +} +/*! + Creates a QScxmlEvent named \a name, with parameter names \a paramNames, parameter values \a paramValues, and + a QtScript object \a content as an additional parameter. +*/ +QScxmlEvent::QScxmlEvent( + const QString & name, + const QStringList & paramNames, + const QVariantList & paramValues, + const QScriptValue & content) + + : QEvent(QScxmlEvent::eventType()),ename(name),pnames(paramNames),pvals(paramValues),cnt(content) +{ + metaData.kind = MetaData::Internal; +} + +/*! \class QScxmlTransition + \brief The QScxmlTransition class stands for a transition that responds to QScxmlEvent, and can be made conditional with a \l conditionExpression. + Equivalent to the SCXML transition tag. + + \ingroup sctools + */ +/*! \property QScxmlTransition::eventPrefix + The event prefix to be used when testing if the transition needs to be invoked. + Uses SCXML prefix matching. Use * to handle any event. + */ +/*! \property QScxmlTransition::conditionExpression + A QtScript expression that's evaluated to test whether the transition needs to be invoked. + */ + +/*! + Creates a new QScxmlTransition from \a state, that uses \a machine to evaluate the conditions. + */ +QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine) + : QAbstractTransition(state),scxml(machine) +{ +} + +/*! + \internal + */ +bool QScxmlTransition::eventTest(QEvent *e) +{ + QScriptEngine* engine = scxml->scriptEngine(); + QString ev; + + if (e) { + if (e->type() == QScxmlEvent::eventType()) { + ev = ((QScxmlEvent*)e)->eventName(); + } + if (!(eventPrefix() == "*" || eventPrefix() == ev || ev.startsWith(eventPrefix()+"."))) + return false; + } + + + if (!conditionExpression().isEmpty()) { + + QScriptValue v = engine->evaluate(conditionExpression(),scxml->baseUrl().toLocalFile()); + if (engine->hasUncaughtException()) { + + qDebug() << engine->uncaughtException().toString(); + QScxmlEvent* e = new QScxmlEvent("error.illegalcond", + QStringList()<< "error" << "expr" << "line" << "backtrace", + QVariantList() + << QVariant(engine->uncaughtException().toString()) + << QVariant(conditionExpression()) + << QVariant(engine->uncaughtExceptionLineNumber()) + << QVariant(engine->uncaughtExceptionBacktrace())); + e->metaData.kind = QScxmlEvent::MetaData::Platform; + scxml->postEvent(e); + engine->clearExceptions(); + return false; + } + return v.toBoolean(); + } + + return true; +} + +class QScxmlDefaultInvoker : public QScxmlInvoker +{ + Q_OBJECT + + + public: + QScxmlDefaultInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),cancelled(false),childSm(0) + { + childSm = QScxml::load (ievent->metaData.origin.resolved(ievent->metaData.target).toLocalFile(),this); + if (childSm == NULL) { + postParentEvent("error.targetunavailable"); + } else { + connect(childSm,SIGNAL(finished()),this,SLOT(deleteLater())); + + } + } + + + + static void initInvokerFactory(QScxml*) {} + + static bool isTypeSupported(const QString & t) { return t.isEmpty() || t.toLower() == "scxml"; } + + public Q_SLOTS: + void activate () + { + if (childSm) + childSm->start(); + } + + void cancel () + { + cancelled = true; + if (childSm) + childSm->stop(); + + } + + private: + bool cancelled; + QScxml* childSm; +}; +class QScxmlBindingInvoker : public QScxmlInvoker +{ + Q_OBJECT + QScriptValue content; + QScriptValue stored; + + public: + QScxmlBindingInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p) + { + } + + static void initInvokerFactory(QScxml*) {} + + static bool isTypeSupported(const QString & t) { return t.toLower() == "q-bindings"; } + + public Q_SLOTS: + void activate () + { + QScriptEngine* engine = ((QScxml*)parent())->scriptEngine(); + QScriptValue content = initEvent->content(); + if (content.isArray()) { + stored = content.engine()->newArray(content.property("length").toInt32()); + + QScriptValueIterator it (content); + for (int i=0; it.hasNext(); ++i) { + it.next(); + if (it.value().isArray()) { + QScriptValue object = it.value().property(0); + QString property = it.value().property(1).toString(); + QScriptValue val = it.value().property(2); + QScriptValue arr = engine->newArray(3); + arr.setProperty("0",it.value().property(0)); + arr.setProperty("1",it.value().property(1)); + if (object.isQObject()) { + QObject* o = object.toQObject(); + arr.setProperty("2",engine->newVariant(o->property(property.toAscii().constData()))); + o->setProperty(property.toAscii().constData(),val.toVariant()); + } else if (object.isObject()) { + arr.setProperty("2",object.property(property)); + object.setProperty(property,val); + } + stored.setProperty(i,arr); + } + } + } + } + + void cancel () + { + if (stored.isArray()) { + QScriptValueIterator it (stored); + while (it.hasNext()) { + it.next(); + if (it.value().isArray()) { + QScriptValue object = it.value().property(0); + QString property = it.value().property(1).toString(); + QScriptValue val = it.value().property(2); + if (object.isQObject()) { + QObject* o = object.toQObject(); + o->setProperty(property.toAscii().constData(),val.toVariant()); + } else if (object.isObject()) { + object.setProperty(property,val); + } + } + } + } + } +}; + +/*! +\fn QScxmlInvoker::~QScxmlInvoker() +*/ + + +/*! +\fn QScxml::eventTriggered(const QString & name) + +This signal is emitted when external event \a name is handled in the state machine. +*/ + +/*! + Creates a new QScxml object, with parent \a parent. + */ + +QScxml::QScxml(QObject* parent) + : QStateMachine(parent) +{ + pvt = new QScxmlPrivate; + pvt->scriptEng = new QScriptEngine(this); + QScriptValue glob = pvt->scriptEng->globalObject(); + QScriptValue utilObj = pvt->scriptEng->newObject(); + glob.setProperty("In",pvt->scriptEng->newFunction(QScxmlFunctions::isInState)); + glob.setProperty("_rcvSig",pvt->scriptEng->newFunction(QScxmlFunctions::receiveSignal)); + glob.setProperty("print",pvt->scriptEng->newFunction(QScxmlFunctions::script_print)); + utilObj.setProperty("postEvent",pvt->scriptEng->newFunction(QScxmlFunctions::postEvent)); + utilObj.setProperty("invoke",pvt->scriptEng->newFunction(QScxmlFunctions::invoke)); + utilObj.setProperty("cssTime",pvt->scriptEng->newFunction(QScxmlFunctions::cssTime)); + utilObj.setProperty("stateMachine",pvt->scriptEng->newQObject(this)); + utilObj.setProperty("clone",pvt->scriptEng->newFunction(QScxmlFunctions::deepCopy)); + utilObj.setProperty("setTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::setTimeout)); + utilObj.setProperty("clearTimeout",pvt->scriptEng->newFunction(QScxmlFunctions::clearTimeout)); + QScriptValue dmObj = pvt->scriptEng->newObject(); + glob.setProperty("_data",pvt->scriptEng->newObject()); + glob.setProperty("_global",pvt->scriptEng->globalObject()); + glob.setProperty("scxml",utilObj); + glob.setProperty("connectSignalToEvent",pvt->scriptEng->evaluate("function(sig,ev) {sig.connect({'e':ev},_rcvSig);}")); + static QScxmlAutoInvokerFactory _s_defaultInvokerFactory; + static QScxmlAutoInvokerFactory _s_bindingInvokerFactory; + registerInvokerFactory(&_s_defaultInvokerFactory); + registerInvokerFactory(&_s_bindingInvokerFactory); + connect(this,SIGNAL(started()),this,SLOT(registerSession())); + connect(this,SIGNAL(stopped()),this,SLOT(unregisterSession())); +#ifdef QT_GUI_LIB + static QScxmlAutoInvokerFactory _s_msgboxInvokerFactory; + static QScxmlAutoInvokerFactory _s_menuInvokerFactory; + registerInvokerFactory(&_s_msgboxInvokerFactory); + registerInvokerFactory(&_s_menuInvokerFactory); +#endif +} + +/*! \internal */ +void QScxml::beginSelectTransitions(QEvent* ev) +{ + QScriptValue eventObj = pvt->scriptEng->newObject(); + if (ev) { + if (ev->type() == QScxmlEvent::eventType()) { + QScxmlEvent* se = (QScxmlEvent*)ev; + eventObj.setProperty("name",qScriptValueFromValue(pvt->scriptEng,se->eventName())); + eventObj.setProperty("target",qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue(se->metaData.target))); + eventObj.setProperty("targettype",qScriptValueFromValue(pvt->scriptEng,se->metaData.targetType)); + eventObj.setProperty("invokeid",qScriptValueFromValue(pvt->scriptEng,se->metaData.invokeID)); + eventObj.setProperty("origin",QScriptValue(qScriptValueFromValue(pvt->scriptEng,QVariant::fromValue(se->metaData.origin)))); + eventObj.setProperty("originType",qScriptValueFromValue(pvt->scriptEng,se->metaData.originType)); + switch (se->metaData.kind) { + case QScxmlEvent::MetaData::Internal: + eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "internal")); + break; + case QScxmlEvent::MetaData::External: + eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "external")); + break; + case QScxmlEvent::MetaData::Platform: + eventObj.setProperty("kind",qScriptValueFromValue(pvt->scriptEng, "platform")); + default: + break; + + } + + QScriptValue dataObj = pvt->scriptEng->newObject(); + int i=0; + foreach (QString s, se->paramNames()) { + QScriptValue v = qScriptValueFromValue(pvt->scriptEng, se->paramValues()[i]); + dataObj.setProperty(QString::number(i),v); + dataObj.setProperty(s,v); + ++i; + } + eventObj.setProperty("data",dataObj); + emit eventTriggered(se->eventName()); + } + } + scriptEngine()->globalObject().setProperty("_event",eventObj); + + QHash curTargets; + + for (int i = pvt->snapshotStack.size()-1; i >= 0 && curTargets.size() < pvt->anchorTransitions.keys().size(); --i) { + if (!curTargets.contains(pvt->snapshotStack.at(i).anchorType)) { + curTargets[pvt->snapshotStack.at(i).anchorType] = pvt->snapshotStack.at(i).state; + } + } + for (QMultiHash::const_iterator it = pvt->anchorTransitions.constBegin(); it != pvt->anchorTransitions.constEnd(); ++it) { + it.value()->setTargetState(curTargets[it.key()]); + } + +} + +/*! \internal */ +void QScxml::endMicrostep(QEvent*) +{ + scriptEngine()->globalObject().setProperty("_event",QScriptValue()); + for (QHash::iterator + it = pvt->curSnapshot.begin(); + it != pvt->curSnapshot.end(); ++it) { + + pvt->snapshotStack.push(it.value()); + + } + if (pvt->snapshotStack.size() > QScxmlPrivate::MaxSnapshots) { + pvt->snapshotStack.remove(0,pvt->snapshotStack.size()-100); + } + pvt->curSnapshot.clear(); +} + +/*! Returns the script engine attached to the state-machine. */ +QScriptEngine* QScxml::scriptEngine () const +{ + return pvt->scriptEng; +} + +/*! + Registers object \a o to the script engine attached to the state machine. + The object can be accessible from global variable \a name. If \a name is not provided, + the object's name is used. If \a recursive is true, all the object's decendants are registered + as global objects, with their respective object names as variable names. +*/ +void QScxml::registerObject (QObject* o, const QString & name, bool recursive) +{ + QString n(name); + if (n.isEmpty()) + n = o->objectName(); + if (!n.isEmpty()) + pvt->scriptEng->globalObject().setProperty(n,pvt->scriptEng->newQObject(o)); + if (recursive) { + QObjectList ol = o->findChildren(); + foreach (QObject* oo, ol) { + if (!oo->objectName().isEmpty()) + registerObject(oo); + } + } +} + +/*! + Posts a QScxmlEvent named \a event, with no payload. + \sa QScxmlEvent + */ +void QScxml::postNamedEvent(const QString & event) +{ + QScxmlEvent* e = new QScxmlEvent(event); + e->metaData.kind = QScxmlEvent::MetaData::External; + postEvent(e); +} +/*! + Executes script \a s in the attached script engine. + If the script fails, a "error.illegalvalue" event is posted to the state machine. +*/ + +void QScxml::executeScript (const QString & s) +{ + pvt->scriptEng->evaluate (s,baseUrl().toLocalFile()); + if (pvt->scriptEng->hasUncaughtException()) { + QScxmlEvent* e = new QScxmlEvent("error.illegalvalue", + QStringList()<< "error" << "expr" << "line" << "backtrace", + QVariantList() + << QVariant(pvt->scriptEng->uncaughtException().toString()) + << QVariant(s) + << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber()) + << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace())); + e->metaData.kind = QScxmlEvent::MetaData::Platform; + postEvent(e); + pvt->scriptEng->clearExceptions(); + } +} + +/*! + Enabled invoker factory \a f to be called from tags. + */ + +void QScxml::registerInvokerFactory (QScxmlInvokerFactory* f) +{ + pvt->invokerFactories << f; + f->init(this); +} + +/*! \class QScxmlInvoker + \brief The QScxmlInvoker class an invoker, which the state-machine context can activate or cancel + with an tag. + + \ingroup sctools + + An invoker is a object that represents an external component that the state machine + can activate when the encompassing state is entered, or cancel when the encompassing + state is exited from. + */ + +/*! \fn QScxmlInvoker::QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* parent) + When reimplementing the constructor, always use the two parameters (\a ievent and \a parent), + as they're called from QScxmlInvokerFactory. +*/ + +/*! \fn QScxmlInvoker::activate() + This function is called when the encompassing state is entered. + The call to this function from the state-machine context is asynchronous, to make sure + that the state is not exited during the same step in which it's entered. + +*/ + +/*! \fn QScxmlInvoker::cancel() + Reimplement this function to allow for asynchronous cancellation of the invoker. + It's the invoker's responsibility to delete itself after this function has been called. + The default implementation deletes the invoker. +*/ + +/*! \fn QScxml* QScxmlInvoker::parentStateMachine() + Returns the state machine encompassing the invoker. + */ + +/*! + Posts an event \a e to the state machine encompassing the invoker. + */ +void QScxmlInvoker::postParentEvent (QScxmlEvent* e) +{ + e->metaData.origin = initEvent->metaData.target; + e->metaData.target = initEvent->metaData.origin; + e->metaData.originType = initEvent->metaData.targetType; + e->metaData.targetType = initEvent->metaData.originType; + e->metaData.kind = QScxmlEvent::MetaData::External; + e->metaData.invokeID = initEvent->metaData.invokeID; + parentStateMachine()->postEvent(e); +} +/*! \overload + Posts a QScxmlEvent named \a e to the encompassing state machine. + */ +void QScxmlInvoker::postParentEvent(const QString & e) +{ + QScxmlEvent* ev = new QScxmlEvent(e); + ev->metaData.kind = QScxmlEvent::MetaData::External; + postParentEvent(ev); +} +/*! \internal */ +QScxml::~QScxml() +{ + delete pvt; +} + +QString QScxmlInvoker::id () const +{ + return initEvent->metaData.invokeID; +} +void QScxmlInvoker::setID(const QString & id) +{ + initEvent->metaData.invokeID = id; +} + +QScxmlInvoker::~QScxmlInvoker() +{ + if (cancelled) + postParentEvent("CancelResponse"); + else + postParentEvent(QString("done.invoke.%1").arg(initEvent->metaData.invokeID)); +} +/*! + \property QScxml::baseUrl + The url used to resolve scripts and invoke urls. +*/ +QUrl QScxml::baseUrl() const +{ + return pvt->burl; +} + +void QScxml::setBaseUrl(const QUrl & u) +{ + pvt->burl = u; +} + +void QScxml::registerSession() +{ + pvt->sessionID = QUuid::createUuid().toString(); + pvt->sessions[pvt->sessionID] = this; + pvt->scriptEng->globalObject().setProperty("_sessionid",qScriptValueFromValue(scriptEngine(), pvt->sessionID)); + executeScript(pvt->startScript); +} + +void QScxml::unregisterSession() +{ + pvt->scriptEng->globalObject().setProperty("_sessionid",QScriptValue()); + pvt->sessions.remove(pvt->sessionID); +} + +/*! + Returns a statically-generated event type to be used by SCXML events. +*/ +QEvent::Type QScxmlEvent::eventType() +{ + static QEvent::Type _t = (QEvent::Type)QEvent::registerEventType(QEvent::User+200); + return _t; +} +const char SCXML_NAMESPACE [] = "http://www.w3.org/2005/07/scxml"; + + + +struct ScTransitionInfo +{ + + QScxmlTransition* transition; + QStringList targets; + QString anchor; + QString script; + ScTransitionInfo() : transition(NULL) {} +}; + +class QScxmlScriptExec : public QObject +{ + Q_OBJECT + QString script; + QScxml* scxml; + public: + QScxmlScriptExec(const QString & scr, QScxml* scx) : script(scr),scxml(scx) + { + } + public Q_SLOTS: + void exec() + { + scxml->executeScript(script); + } +}; + +struct ScStateInfo +{ + QString initial; +}; + +struct ScHistoryInfo +{ + QHistoryState* hstate; + QString defaultStateID; +}; + +struct ScExecContext +{ + QScxml* sm; + QString script; + enum {None, StateEntry,StateExit,Transition } type; + QScxmlTransition* trans; + QAbstractState* state; + ScExecContext() : sm(NULL),type(None),trans(NULL),state(NULL) + { + } + + void applyScript() + { + if (!script.isEmpty()) { + QScxmlScriptExec* exec = new QScxmlScriptExec(script,sm); + switch(type) { + case StateEntry: + QObject::connect(state,SIGNAL(entered()),exec,SLOT(exec())); + break; + case StateExit: + QObject::connect(state,SIGNAL(exited()),exec,SLOT(exec())); + break; + case Transition: + QObject::connect(trans,SIGNAL(activated()),exec,SLOT(exec())); + break; + default: + delete exec; + break; + } + } + } +}; + +class QScxmlLoader +{ + public: + QScxml* stateMachine; + + QList transitions; + QHash stateInfo; + QList historyInfo; + QHash stateByID; + QSet signalEvents; + QSet statesWithFinal; + void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename); + QScxml* load (QIODevice* device, QObject* obj = NULL, const QString & filename = ""); + + QScriptValue evaluateFile (const QString & fn) + { + QFile f (fn); + f.open(QIODevice::ReadOnly); + return stateMachine->scriptEngine()->evaluate(QString::fromUtf8(f.readAll()),fn); + } +}; + +class QScxmlAnchorSave : public QObject +{ + Q_OBJECT + public: + QScxml* sm; + QScxmlPrivate* pvt; + QScxmlPrivate::AnchorSnapshot anchorSnapshot; + QScxmlAnchorSave(QScxml* p,QScxmlPrivate* pv, + const QString & type, const QString & loc, + QAbstractState* s) : + QObject(p),sm(p),pvt(pv) + { + anchorSnapshot.anchorType = type; + anchorSnapshot.location = loc; + anchorSnapshot.state = s; + } + + public Q_SLOTS: + + void save() + { + if (!anchorSnapshot.location.isEmpty()) { + anchorSnapshot.snapshot = _q_deepCopy(sm->scriptEngine()->evaluate(anchorSnapshot.location)); + } + pvt->curSnapshot[anchorSnapshot.anchorType] = anchorSnapshot; + } +}; + +class QScxmlAnchorRestore : public QObject +{ + Q_OBJECT + public: + QScxml* sm; + QScxmlPrivate* pvt; + QString anchorType; + QScxmlAnchorRestore(QScxml* p,QScxmlPrivate* pv, + const QString & type) : + QObject(p),sm(p),pvt(pv),anchorType(type) + { + + } + + public Q_SLOTS: + void restore () + { + pvt->curSnapshot.clear(); + while (!pvt->snapshotStack.isEmpty()) { + QScxmlPrivate::AnchorSnapshot s = pvt->snapshotStack.pop(); + if (s.anchorType == anchorType) { + if (s.location != "") { + sm->scriptEngine()->globalObject().setProperty("_snapshot",s.snapshot); + sm->scriptEngine()->evaluate(QString ("%1 = _snapshot;").arg(s.location)); + sm->scriptEngine()->globalObject().setProperty("_snapshot",QScriptValue()); + } + break; + } + } + } +}; + +static QString sanitize (const QString & str) +{ + return str; +// return QString("eval(unescape(\"%1\"))"). +// arg(QString::fromAscii(str.trimmed().toUtf8().toPercentEncoding(QByteArray("[]()<>;:#/'`_-., \t@!^&*{}")))); +} +static QString sanitize (const QStringRef & str) +{ + return sanitize(str.toString()); +} + +void QScxmlLoader::loadState ( + QState* stateParam, + QIODevice *dev, + const QString & stateID, + const QString & filename) +{ + QXmlStreamReader r (dev); + QState* curState = NULL; + ScExecContext curExecContext; + curExecContext.sm = stateMachine; + QState* topLevelState = NULL; + QHistoryState* curHistoryState = NULL; + QString initialID = ""; + QString idLocation; + QScxmlTransition* curTransition = NULL; + bool inRoot = true; + while (!r.atEnd()) { + r.readNext(); + if (r.hasError()) { + qDebug() << QString("SCXML read error at line %1, column %2: %3").arg(r.lineNumber()).arg(r. columnNumber()).arg(r.errorString()); + return; + } + if (r.namespaceUri() == SCXML_NAMESPACE || r.namespaceUri() == "") { + if (r.isStartElement()) { + if (r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) { + if (stateID == "") { + topLevelState = curState = stateParam; + stateInfo[curState].initial = r.attributes().value("initial").toString(); + if (curState == stateMachine->rootState()) { + stateMachine->scriptEngine()->globalObject().setProperty("_name",qScriptValueFromValue(stateMachine->scriptEngine(),r.attributes().value("name").toString())); + } + + } + } else if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) { + inRoot = false; + QString id = r.attributes().value("id").toString(); + QState* newState = NULL; + if (curState) { + newState= new QState(r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0 ? QState::ParallelStates : QState::ExclusiveStates, + curState); + } else if (id == stateID) { + topLevelState = newState = stateParam; + + } + if (newState) { + stateInfo[newState].initial = r.attributes().value("initial").toString(); + newState->setObjectName(id); + if (!id.isEmpty() && stateInfo[curState].initial == id) { + + if (curState == stateMachine->rootState()) + stateMachine->setInitialState(newState); + else + curState->setInitialState(newState); + } + QString src = r.attributes().value("src").toString(); + if (!src.isEmpty()) { + int refidx = src.indexOf('#'); + QString srcfile, refid; + if (refidx > 0) { + srcfile = src.left(refidx); + refid = src.mid(refidx+1); + } else + srcfile = src; + srcfile = QDir::cleanPath( QFileInfo(filename).dir().absoluteFilePath(srcfile)); + QFile newFile (srcfile); + if (newFile.exists()) { + newFile.open(QIODevice::ReadOnly); + loadState(newState,&newFile,refid,srcfile); + } + } + initialID = r.attributes().value("initial").toString(); + stateByID[id] = newState; + curState = newState; + curExecContext.state = newState; + } + + } else if (r.name().toString().compare("initial",Qt::CaseInsensitive) == 0) { + if (curState && stateInfo[curState].initial == "") { + QState* newState = new QState(curState); + curState->setInitialState(newState); + } + } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) { + if (curState) { + QString id = r.attributes().value("id").toString(); + curHistoryState = new QHistoryState(r.attributes().value("type") == "shallow" ? QHistoryState::ShallowHistory : QHistoryState::DeepHistory,curState); + curHistoryState->setObjectName(id); + stateByID[id] = curHistoryState; + } + } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) { + if (curState) { + QString id = r.attributes().value("id").toString(); + QFinalState* f = new QFinalState(curState); + f->setObjectName(id); + curExecContext.state = f; + statesWithFinal.insert(curState); + QState* gp = qobject_cast(curState->parentState()); + if (gp->childMode() == QState::ParallelStates) { + statesWithFinal.insert(gp); + } + stateByID[id] = f; + } + } else if (r.name().toString().compare("script",Qt::CaseInsensitive) == 0) { + QString txt = r.readElementText().trimmed(); + if (curExecContext.type == ScExecContext::None && curState == topLevelState) { + stateMachine->executeScript(txt); + } else + curExecContext.script += txt; + } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) { + curExecContext.script += + QString("print('[' + %1 + '][' + %2 + ']' + %3);") + .arg(sanitize(r.attributes().value("label"))) + .arg(sanitize(r.attributes().value("level"))) + .arg(sanitize(r.attributes().value("expr"))); + + } else if (r.name().toString().compare("assign",Qt::CaseInsensitive) == 0) { + QString locattr = r.attributes().value("location").toString(); + if (locattr.isEmpty()) { + locattr = r.attributes().value("dataid").toString(); + if (!locattr.isEmpty()) + locattr = "_data." + locattr; + } + if (!locattr.isEmpty()) { + curExecContext.script += QString ("%1 = %2;").arg(locattr).arg(sanitize(r.attributes().value("expr"))); + } + } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) { + curExecContext.script += QString("if (%1) {").arg(sanitize(r.attributes().value("cond"))); + } else if (r.name().toString().compare("elseif",Qt::CaseInsensitive) == 0) { + curExecContext.script += QString("} elseif (%1) {").arg(sanitize(r.attributes().value("cond"))); + } else if (r.name().toString().compare("else",Qt::CaseInsensitive) == 0) { + curExecContext.script += " } else { "; + } else if (r.name().toString().compare("cancel",Qt::CaseInsensitive) == 0) { + curExecContext.script += QString("scxml.clearTimeout (%1);").arg(sanitize(r.attributes().value("id"))); + } else if (r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0) { + curExecContext.type = ScExecContext::StateEntry; + curExecContext.script = ""; + } else if (r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0) { + curExecContext.type = ScExecContext::StateExit; + curExecContext.script = ""; + } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0 || r.name().toString().compare("event",Qt::CaseInsensitive) == 0 ) { + QString ev = r.attributes().value("event").toString(); + if (ev.isEmpty()) + ev = r.attributes().value("name").toString(); + curExecContext.script += + QString("{" + "var paramNames = []; var paramValues = []; " + "var content = ''; var eventName='%1'; " + "var target = '_internal'; var targetType = 'scxml'; ").arg(ev); + + } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) { + QString type = r.attributes().value("type").toString(); + if (type.isEmpty()) + type = r.attributes().value("targettype").toString(); + curExecContext.script += + QString("{" + "var paramNames = [%1]; var paramValues = []; " + "var content = ''; var eventName=%2; " + "var targetType = %3; var target = %4;") + .arg(r.attributes().value("namelist").toString().replace(" ",",")) + .arg(sanitize(r.attributes().value("event").toString())) + .arg(type.isEmpty() ? "'scxml'" : sanitize(r.attributes().value("type"))) + .arg(r.attributes().value("target").length() ? sanitize(r.attributes().value("target")) : "''"); + idLocation = r.attributes().value("idlocation").toString(); + if (idLocation.isEmpty()) + idLocation = r.attributes().value("sendid").toString(); + + curExecContext.script += QString("var delay = %1; ").arg(r.attributes().value("delay").length() + ? QString("scxml.cssTime(%1)").arg(sanitize(r.attributes().value("delay"))) + : "0"); + } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) { + idLocation = r.attributes().value("idlocation").toString(); + if (idLocation.isEmpty()) + idLocation = r.attributes().value("invokeid").toString(); + QObject::connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec())); + + QString type = r.attributes().value("type").toString(); + if (type.isEmpty()) + type = r.attributes().value("targettype").toString(); + curExecContext.type = ScExecContext::StateEntry; + curExecContext.state = curState; + curExecContext.script = + QString("{" + "var paramNames = []; var paramValues = []; " + "var content = ''; " + "var srcType = \"%1\"; var src = %2;") + .arg(type.length() ? type : "scxml") + .arg(r.attributes().value("src").length() ? sanitize(r.attributes().value("target")) : "\"\""); + + + } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) { + if (curHistoryState) { + ScHistoryInfo inf; + inf.hstate = curHistoryState; + inf.defaultStateID = r.attributes().value("target").toString(); + historyInfo.append(inf); + } else { + ScTransitionInfo inf; + inf.targets = r.attributes().value("target").toString().split(' '); + curExecContext.type = ScExecContext::Transition; + curExecContext.script = ""; + curTransition = new QScxmlTransition(curState,stateMachine); + curTransition->setConditionExpression(r.attributes().value("cond").toString()); + curTransition->setEventPrefix(r.attributes().value("event").toString()); + curExecContext.trans = curTransition; + QString anc = r.attributes().value("anchor").toString(); + if (!anc.isEmpty()) { + stateMachine->pvt->anchorTransitions.insert(anc,curTransition); + QObject::connect (curTransition, SIGNAL(activated()),new QScxmlAnchorRestore(stateMachine,stateMachine->pvt,anc),SLOT(restore())); + } + inf.transition = curTransition; + transitions.append(inf); + if (curTransition->eventPrefix().startsWith("q-signal:")) { + signalEvents.insert(curTransition->eventPrefix()); + } + curTransition->setObjectName(QString ("%1 to %2 on %3 if %4 (anchor=%5)").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefix()).arg(curTransition->conditionExpression()).arg(anc)); + } + } else if (r.name().toString().compare("anchor",Qt::CaseInsensitive) == 0) { + QObject::connect(curState,SIGNAL(exited()),new QScxmlAnchorSave(stateMachine,stateMachine->pvt,r.attributes().value("type").toString(),r.attributes().value("snapshot").toString(),curState),SLOT(save())); + } else if (r.name().toString().compare("data",Qt::CaseInsensitive) == 0) { + QScriptValue val = qScriptValueFromValue(stateMachine->scriptEngine(),"") ; + QString id = r.attributes().value("id").toString(); + if (r.attributes().value("src").length()) + val = evaluateFile(QFileInfo(filename).dir().absoluteFilePath(r.attributes().value("src").toString())); + else { + if (r.attributes().value("expr").length()) { + val = stateMachine->scriptEngine()->evaluate(r.attributes().value("expr").toString()); + } else { + QString t = r.readElementText(); + if (!t.isEmpty()) + val = stateMachine->scriptEngine()->evaluate(t); + } + } + stateMachine->scriptEngine()->evaluate("_data") + .setProperty(id,val); + } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) { + curExecContext.script += + QString("paramNames[paramNames.length] = \"%1\";") + .arg(r.attributes().value("name").toString()); + curExecContext.script += + QString("paramValues[paramValues.length] = %1;") + .arg(sanitize(r.attributes().value("expr"))); + + } else if (r.name().toString().compare("content",Qt::CaseInsensitive) == 0) { + curExecContext.script += QString("content = %1; ").arg(sanitize(r.readElementText())); + } + } else if (r.isEndElement()) { + if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) { + if (curState == topLevelState) { + return; + } else { + curState = qobject_cast(curState->parent()); + curExecContext.state = curState; + } + } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) { + curHistoryState = NULL; + } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) { + curExecContext.state = (curExecContext.state->parentState()); + } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) { + if (!idLocation.isEmpty()) + curExecContext.script += idLocation + " = "; + curExecContext.script += QString("scxml.setTimeout(function() { " + "scxml.postEvent(" + "eventName,target,targetType,paramNames,paramValues,content" + ");" + "}, delay); }"); + idLocation = ""; + } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0) { + curExecContext.script += "scxml.postEvent(eventName,target,targetType,paramNames,paramValues,content); }"; + } else if ( + r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0 + || r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0 + || r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) { + curExecContext.state = curState; + curExecContext.type = r.name().toString().compare("onexit",Qt::CaseInsensitive)==0 ? ScExecContext::StateExit : ScExecContext::StateEntry; + curExecContext.applyScript(); + curExecContext.type = ScExecContext::None; + } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) { + if (!curHistoryState) { + curExecContext.trans = curTransition; + curExecContext.type = ScExecContext::Transition; + curExecContext.applyScript(); + } + + ScTransitionInfo* ti = &(transitions.last()); + if (!curExecContext.script.isEmpty() && ti->anchor != "") + ti->script = curExecContext.script; + curExecContext.type = ScExecContext::None; + } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) { + curExecContext.script += QString("invoke_%1 = scxml.invoke(srcType,src,paramNames,paramValues,content); }").arg(curState->objectName()); + if (!idLocation.isEmpty()) { + curExecContext.script += QString("%1 = invoke_%2;").arg(idLocation).arg(curState->objectName()); + } + curExecContext.state = curState; + curExecContext.type = ScExecContext::StateEntry; + curExecContext.applyScript(); + idLocation = ""; + curExecContext.type = ScExecContext::None; + } + } + } + } +} + + +QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename) +{ + stateMachine = new QScxml(obj); + // traverse through the states + loadState(stateMachine->rootState(),device,"",filename); + + // resolve history default state + foreach (ScHistoryInfo h, historyInfo) { + h.hstate->setDefaultState(stateByID[h.defaultStateID]); + } + foreach (QString s, signalEvents) { + QString sig = s; + sig = sig.mid(sig.indexOf(':')+1); + sig = sig.left(sig.indexOf('(')); + QString scr = QString("%1.connect({e:\"%2\"},_rcvSig);\n").arg(sig).arg(s); + stateMachine->pvt->startScript += scr; + } + + foreach (QState* s, statesWithFinal) { + QObject::connect(s,SIGNAL(finished()),stateMachine,SLOT(handleStateFinished())); + } + + // resolve transitions + + foreach (ScTransitionInfo t, transitions) { + QList states; + if (!t.targets.isEmpty()) { + foreach (QString s, t.targets) { + if (!s.trimmed().isEmpty()) { + QAbstractState* st = stateByID[s]; + if (st) + states.append(st); + } + } + t.transition->setTargetStates(states); + } + } + + return stateMachine; +} + +void QScxml::handleStateFinished() +{ + QState* state = qobject_cast(sender()); + if (state) { + postEvent(new QScxmlEvent("done.state." + state->objectName())); + } +} + +/*! + Loads a state machine from an scxml file located at \a filename, with parent object \a o. + */ +QScxml* QScxml::load (const QString & filename, QObject* o) +{ + QScxmlLoader l; + QFile f (filename); + f.open(QIODevice::ReadOnly); + return l.load(&f,o,filename); +} + +#include "qscxml.moc" diff --git a/src/qscxml.h b/src/qscxml.h new file mode 100644 index 0000000..255b33a --- /dev/null +++ b/src/qscxml.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCXML_H +#define QSCXML_H +#ifndef QT_NO_STATEMACHINE + +#include +#include +#include +#include +#include +#include +#include + +class QScriptEngine; +class QScxml; + +class QScxmlEvent : public QEvent +{ + public: + static QEvent::Type eventType(); + QString eventName() const; + QStringList paramNames () const; + QVariantList paramValues () const; + QScriptValue content () const; + QVariant param (const QString & name) const; + QScxmlEvent ( + const QString & name, + const QStringList & paramNames = QStringList(), + const QVariantList & paramValues = QVariantList(), + const QScriptValue & content = QScriptValue()); + + struct MetaData + { + QUrl origin,target; + QString originType, targetType; + QString invokeID; + enum Kind { Platform, Internal, External } kind; + }; + + MetaData metaData; + + private: + QString ename; + QStringList pnames; + QVariantList pvals; + QScriptValue cnt; +}; + + +class QScxmlTransition : public QAbstractTransition +{ + Q_OBJECT + Q_PROPERTY(QString conditionExpression READ conditionExpression WRITE setConditionExpression) + Q_PROPERTY(QString eventPrefix READ eventPrefix WRITE setEventPrefix) + + public: + QScxmlTransition (QState* state, QScxml* machine); + + QString conditionExpression () const { return cond; } + void setConditionExpression (const QString & c) { cond = c; } + QString eventPrefix () const { return ev; } + void setEventPrefix (const QString & e) { ev = e; } + + Q_SIGNALS: + void activated (); + protected: + bool eventTest(QEvent*); + void onTransition (QEvent*) { emit activated(); } + private: + QScxml* scxml; + QString ev,cond; +}; + +class QScxmlInvoker : public QObject +{ + Q_OBJECT + Q_PROPERTY (QString id READ id WRITE setID) + + protected: + QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* p) : QObject(p), initEvent(ievent),cancelled(false) {} + + public: + virtual ~QScxmlInvoker(); + QString id () const; + void setID(const QString &); + + public Q_SLOTS: + virtual void activate() = 0; + virtual void cancel() { deleteLater(); } + + protected Q_SLOTS: + void postParentEvent (const QString & event); + + protected: + QScxml* parentStateMachine() { return (QScxml*)parent(); } + void postParentEvent (QScxmlEvent* ev); + QScxmlEvent* initEvent; + bool cancelled; + + friend struct QScxmlFunctions; +}; + +struct QScxmlInvokerFactory +{ + virtual QScxmlInvoker* createInvoker (QScxmlEvent* event, QScxml* stateMachine) = 0; + virtual bool isTypeSupported (const QString & type) const = 0; + virtual void init (QScxml*) = 0; +}; + +template +class QScxmlAutoInvokerFactory : public QScxmlInvokerFactory +{ + QScxmlInvoker* createInvoker (QScxmlEvent* _e, QScxml* _sm) { return new T(_e,_sm); } + bool isTypeSupported(const QString & _s) const { return T::isTypeSupported(_s); } + void init (QScxml* sm) { T::initInvokerFactory(sm); } +}; + + +class QScxml : public QStateMachine +{ + Q_OBJECT + + Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl) + + + public: + QScxml(QObject* o = NULL); + virtual ~QScxml(); + protected: + // overloaded to store the event for the script environment's use (_event), and to convert + // StateFinished events to "done." named events + virtual void beginSelectTransitions(QEvent*); + virtual void endMicrostep(QEvent*); + + public: + QScriptEngine* scriptEngine () const; + void registerObject (QObject* object, const QString & name = QString(), bool recursive = false); + void registerInvokerFactory (QScxmlInvokerFactory* f); + void setBaseUrl (const QUrl &); + QUrl baseUrl () const; + static QScxml* load (const QString & filename, QObject* o = NULL); + + public Q_SLOTS: + void postNamedEvent(const QString &); + void executeScript (const QString &); + + private Q_SLOTS: + void registerSession(); + void unregisterSession(); + void handleStateFinished(); + + Q_SIGNALS: + void eventTriggered(const QString &); + + private: + class QScxmlPrivate* pvt; + 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 +#include +#include +#include +#include +#include +/* + + { "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(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(se)); + } + void QScxmlMessageBoxInvoker::initInvokerFactory(QScxml* sm) + { + QScriptEngine* se = sm->scriptEngine(); + se->globalObject().setProperty("QMessageBox",qScriptValueFromQMetaObject(se)); + } + +void QScxmlMessageBoxInvoker::onFinished(int n) { + postParentEvent(new QScxmlEvent("q-messagebox.finished",QStringList()<<"result",QVariantList()<content(); + QWidget* parent = qobject_cast(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 -- cgit v1.2.3