a6553f6 by Noam Rosenthal at 2009-06-07 1
/****************************************************************************
2
**
3d88526 by No'am Rosenthal at 2009-11-26 3
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
a6553f6 by Noam Rosenthal at 2009-06-07 6
**
3d88526 by No'am Rosenthal at 2009-11-26 7
** This file is part of SCXML on Qt labs
a6553f6 by Noam Rosenthal at 2009-06-07 8
**
3d88526 by No'am Rosenthal at 2009-11-26 9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
a6553f6 by Noam Rosenthal at 2009-06-07 39
**
40
****************************************************************************/
3d88526 by No'am Rosenthal at 2009-11-26 41
a6553f6 by Noam Rosenthal at 2009-06-07 42
/*!
43
  \class QScxml
44
45
  \brief The QScxml class provides a way to use scripting with the Qt State Machine Framework.
46
47
  Though can be used alone, QScxml is mainly a runtime helper to using the
48
  state-machine framework with SCXML files.
49
50
51
  \sa QStateMachine
52
*/
7a41ce5 by Noam Rosenthal at 2009-06-08 53
#ifndef QT_NO_STATEMACHINE
a6553f6 by Noam Rosenthal at 2009-06-07 54
55
#include "qscxml.h"
56
#include <QScriptEngine>
57
#include <QScriptValueIterator>
58
#include <QDebug>
59
#include <QTimer>
60
#include <QSignalMapper>
61
#include <QUuid>
62
#include <QHash>
63
#include <QXmlStreamReader>
64
#include <QFileInfo>
65
#include <QDir>
66
#include <QSet>
67
#include <QStack>
d0441f6 by Noam Rosenthal at 2009-06-08 68
#include <QHistoryState>
69
#include <QFinalState>
70
#include <QState>
65e5705 by Noam Rosenthal at 2009-06-15 71
#include <QMetaMethod>
7a19605 by No'am Rosenthal at 2009-11-26 72
#include <QScriptProgram>
a6553f6 by Noam Rosenthal at 2009-06-07 73
74
65e5705 by Noam Rosenthal at 2009-06-15 75
76
class QtScxmlSnoopInternal : public QObject
77
    {
78
        Q_OBJECT
79
80
        friend class QtScxmlSnoop;
81
82
        QtScxmlSnoopInternal(QObject* o) :QObject(o) { }
83
        ~QtScxmlSnoopInternal() { 
84
            if (parent()) parent()->deleteLater(); 
85
            }
86
87
        signals:
88
            void signal (const QVariantList &);
89
    };
90
    class QtScxmlSnoop: public QObject
91
    {
92
        public:
93
            QtScxmlSnoopInternal* inobj;
94
            QtScxmlSnoop(QObject *obj, const char *aSignal):QObject(obj)
95
            {
96
#ifdef Q_CC_BOR
97
                const int memberOffset = QObject::staticMetaObject.methodCount();
98
#else
99
                static const int memberOffset = QObject::staticMetaObject.methodCount();
100
#endif
101
                Q_ASSERT(obj);
102
                Q_ASSERT(aSignal);
103
104
                if (aSignal[0] - '0' != QSIGNAL_CODE) {
105
                    qWarning("QtScxmlSnoop: Not a valid signal, use the SIGNAL macro");
106
                    return;
107
                }
108
109
                QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
110
                const QMetaObject *mo = obj->metaObject();
111
                int sigIndex = mo->indexOfMethod(ba.constData());
112
                if (sigIndex < 0) {
113
                    qWarning("QtScxmlSnoop: No such signal: '%s'", ba.constData());
114
                    return;
115
                }
116
117
                if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
118
                     Qt::QueuedConnection, 0)) {
119
                         qWarning("QtScxmlSnoop: QMetaObject::connect returned false. Unable to connect.");
120
                         return;
121
                     }
122
                     sig = ba;
123
                     QMetaMethod member = mo->method(sigIndex);
124
                     QList<QByteArray> params = member.parameterTypes();
125
                     for (int i = 0; i < params.count(); ++i) {
126
                         int tp = QMetaType::type(params.at(i).constData());
127
                         if (tp == QMetaType::Void)
128
                             qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
129
                                      params.at(i).constData());
130
                         args << tp;
131
                     }
132
                inobj = new QtScxmlSnoopInternal (this);
133
            }
134
135
            inline bool isValid() const { return !sig.isEmpty(); }
136
            inline QByteArray signal() const { return sig; }
137
138
139
            int qt_metacall(QMetaObject::Call call, int id, void **a)
140
            {
141
                id = QObject::qt_metacall(call, id, a);
142
                if (id < 0)
143
                    return id;
144
145
                if (call == QMetaObject::InvokeMetaMethod) {
146
                    if (id == 0) {
147
                        QVariantList list;
148
                        for (int i = 0; i < args.count(); ++i) {
149
                            QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
150
                            QVariant v(type, a[i + 1]);
151
                            list << v;
152
153
                        }
154
                        emit inobj->signal (list);
155
                    }
156
                    --id;
157
                }
158
                return id;
159
            }
160
161
162
163
    // the full, normalized signal name
164
            QByteArray sig;
165
    // holds the QMetaType types for the argument list of the signal
166
            QList<int> args;
167
168
    };
169
170
QObject* q_snoopConnect (
171
                                      QObject* sender,
172
                                      const char* signal,
173
                                      QObject* receiver,
174
                                      const char* method
175
                                     )
176
{
177
    QtScxmlSnoop* o = new QtScxmlSnoop(sender,signal);
178
    if (o->isValid()) {
179
        QObject::connect (o->inobj, SIGNAL(signal(QVariantList)),receiver,method);
180
        QObject::connect (receiver, SIGNAL(destroyed()), o, SLOT(deleteLater()));
181
    }
182
    return o;
183
}
184
a6553f6 by Noam Rosenthal at 2009-06-07 185
class QScxmlPrivate
186
{
187
    public:
188
084833d by No'am Rosenthal at 2009-07-13 189
        void initScriptEngine(QScxml* thiz);
190
7a19605 by No'am Rosenthal at 2009-11-26 191
        QScriptValue dataObj;
192
a6553f6 by Noam Rosenthal at 2009-06-07 193
194
        QScriptEngine* scriptEng;
195
        QList<QScxmlInvokerFactory*> invokerFactories;
196
        QUrl burl;
197
        QString sessionID;
198
        QString startScript;
199
7a19605 by No'am Rosenthal at 2009-11-26 200
        QSet<QString> knownEvents;
201
a6553f6 by Noam Rosenthal at 2009-06-07 202
        static QHash<QString,QScxml*> sessions;
203
};
204
QHash<QString,QScxml*> QScxmlPrivate::sessions;
205
206
class QScxmlTimer : public QObject
207
{
208
    Q_OBJECT
209
    public:
7a19605 by No'am Rosenthal at 2009-11-26 210
        QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine)
a6553f6 by Noam Rosenthal at 2009-06-07 211
        {
7a41ce5 by Noam Rosenthal at 2009-06-08 212
            QTimer::singleShot(delay,this,SLOT(exec()));
7a19605 by No'am Rosenthal at 2009-11-26 213
            if (scr.isString()) {
214
                script = engine->evaluate(QString("function(){%1}").arg(scr.toString()));
215
            } else
216
                script = scr;
a6553f6 by Noam Rosenthal at 2009-06-07 217
        }
7a41ce5 by Noam Rosenthal at 2009-06-08 218
    protected Q_SLOTS:
219
        void exec()
a6553f6 by Noam Rosenthal at 2009-06-07 220
        {
7a19605 by No'am Rosenthal at 2009-11-26 221
a6553f6 by Noam Rosenthal at 2009-06-07 222
            if (script.isFunction())
223
                script.call();
7a41ce5 by Noam Rosenthal at 2009-06-08 224
            deleteLater();
a6553f6 by Noam Rosenthal at 2009-06-07 225
        }
226
        
227
    private:
7a19605 by No'am Rosenthal at 2009-11-26 228
        QScriptValue script;
a6553f6 by Noam Rosenthal at 2009-06-07 229
    
230
};
231
232
static QScriptValue _q_deepCopy(const QScriptValue & val)
233
{
234
    if (val.isObject() || val.isArray()) {
235
        QScriptValue v = val.isArray() ? val.engine()->newArray() : val.engine()->newObject();
236
        v.setData(val.data());
237
        QScriptValueIterator it (val);
238
        while (it.hasNext()) {
239
            it.next();
240
            v.setProperty(it.name(), _q_deepCopy(it.value()));
241
        }
242
        return v;
243
    } else
244
        return val;
245
}
65e5705 by Noam Rosenthal at 2009-06-15 246
class QScxmlSignalReceiver : public QObject
247
{
248
    Q_OBJECT
249
    QScxml* scxml;
250
    QString eventName;
251
    public:
252
        QScxmlSignalReceiver(QScxml* s, QString ename) : QObject(s),scxml(s),eventName(ename)
253
        {
254
        }
255
    public Q_SLOTS:
256
        void  receiveSignal(const QVariantList & pvals)
257
        {
258
            QStringList pnames;
259
            for (int i=0; i < pvals.count(); ++i) {
260
                pnames << QString::number(i);
261
            }
262
            QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,QScriptValue());
263
            ev->metaData.kind = QScxmlEvent::MetaData::Platform;
264
            scxml->postEvent(ev);
265
        }
266
};
a6553f6 by Noam Rosenthal at 2009-06-07 267
struct QScxmlFunctions
268
{
084833d by No'am Rosenthal at 2009-07-13 269
static QScriptValue connectSignalToEvent(QScriptContext* context, QScriptEngine*)
65e5705 by Noam Rosenthal at 2009-06-15 270
{
271
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
272
    if (scxml) {
273
        QObject* obj = context->argument(0).toQObject();
274
        QString sig = ('0'+QSIGNAL_CODE)+context->argument(1).toString();
275
        QString ename = context->argument(2).toString();
276
        
277
        if (obj)
278
        {
279
            q_snoopConnect(obj,sig.toAscii().constData(),new QScxmlSignalReceiver(scxml,ename),SLOT(receiveSignal(QVariantList)));
280
        }
281
    }
282
    return QScriptValue();
283
}
284
a6553f6 by Noam Rosenthal at 2009-06-07 285
static QScriptValue cssTime(QScriptContext *context, QScriptEngine *engine)
286
{
287
    QString str;
288
    if (context->argumentCount() > 0)
289
        str = context->argument(0).toString();
290
    if (str == "") {
291
        return qScriptValueFromValue<int>(engine,0);
292
    }
293
    else if (str.endsWith("ms")) {
294
        return qScriptValueFromValue<int>(engine,(str.left(str.length()-2).toInt()));
295
    }
296
    else if (str.endsWith("s")) {
297
        return qScriptValueFromValue<int>(engine,(str.left(str.length()-1).toInt())*1000);
298
    }
299
    else {
300
        return qScriptValueFromValue<int>(engine, (str.toInt()));
301
    }
302
}
303
static QScriptValue setTimeout(QScriptContext *context, QScriptEngine *engine)
304
{
305
        if (context->argumentCount() < 2)
306
                return QScriptValue();
307
        int timeout = context->argument(1).toInt32();
308
        QScxmlTimer* tmr = new QScxmlTimer(engine,context->argument(0),timeout);
309
        return engine->newQObject(tmr);
310
}
311
static QScriptValue script_print(QScriptContext *context, QScriptEngine *)
312
{
313
    if (context->argumentCount() > 0)
314
        qDebug() << context->argument(0).toString();
315
    return QScriptValue();
316
}
317
static QScriptValue clearTimeout(QScriptContext *context, QScriptEngine *)
318
{
319
        if (context->argumentCount() > 0) {
320
        QObject* obj = context->argument(0).toQObject();
321
        obj->deleteLater();
322
    }
323
        return QScriptValue();
324
}
325
326
static QScriptValue deepCopy(QScriptContext *context, QScriptEngine *)
327
{
328
    if (context->argumentCount() == 0)
329
        return QScriptValue();
330
    else
331
        return _q_deepCopy(context->argument(0));
332
}
333
084833d by No'am Rosenthal at 2009-07-13 334
static QScriptValue postEvent(QScriptContext *context, QScriptEngine *)
a6553f6 by Noam Rosenthal at 2009-06-07 335
{
7a41ce5 by Noam Rosenthal at 2009-06-08 336
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
a6553f6 by Noam Rosenthal at 2009-06-07 337
    if (scxml) {
338
        QString eventName,target,type;
339
        QStringList pnames;
340
        QVariantList pvals;
341
        QScriptValue cnt;
342
        if (context->argumentCount() > 0)
343
            eventName = context->argument(0).toString();
344
        if (context->argumentCount() > 1)
345
            target = context->argument(1).toString();
346
        if (context->argumentCount() > 2)
347
            type = context->argument(2).toString();
348
349
        if (!eventName.isEmpty() || !target.isEmpty()) {
350
            if (context->argumentCount() > 3)
351
                qScriptValueToSequence<QStringList>(context->argument(3),pnames);
352
            if (context->argumentCount() > 4) {
353
                QScriptValueIterator it (context->argument(4));
354
                while (it.hasNext()) {
355
                    it.next();
356
                    pvals.append(it.value().toVariant());
357
                }
358
            } if (context->argumentCount() > 5)
359
                cnt = context->argument(5);
360
            QScxmlEvent* ev = new QScxmlEvent(eventName,pnames,pvals,cnt);
361
            if (type == "scxml" || type == "") {
362
                bool ok = true;
363
                if (target == "_internal") {
364
                    ev->metaData.kind = QScxmlEvent::MetaData::Internal;
9566cee by No'am Rosenthal at 2009-10-03 365
                    scxml->postEvent(ev,QStateMachine::HighPriority);
a6553f6 by Noam Rosenthal at 2009-06-07 366
                } else if (target == "scxml" || target == "") {
367
                    ev->metaData.kind = QScxmlEvent::MetaData::External;
368
                    scxml->postEvent(ev);
369
                } else if (target == "_parent") {
370
                    QScxmlInvoker* p = qobject_cast<QScxmlInvoker*>(scxml->parent());
371
                    if (p)
372
                        p->postParentEvent(ev);
373
                    else
374
                        ok = false;
375
                } else {
376
                    QScxml* session = QScxmlPrivate::sessions[target];
377
                    if (session) {
378
                        session->postEvent(ev);
379
                    } else
380
                        ok = false;
381
                }
382
                if (!ok)
383
                    scxml->postNamedEvent("error.targetunavailable");
384
385
            } else {
386
                scxml->postNamedEvent("error.send.typeinvalid");
387
            }
388
        }
389
    }
390
    return QScriptValue();
391
}
392
393
// scxml.invoke (type, target, paramNames, paramValues, content)
394
static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine)
395
{
7a41ce5 by Noam Rosenthal at 2009-06-08 396
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
a6553f6 by Noam Rosenthal at 2009-06-07 397
    if (scxml) {
398
        QString type,target;
399
        QStringList pnames;
400
        QVariantList pvals;
401
        QScriptValue cnt;
402
        if (context->argumentCount() > 0)
403
            type = context->argument(0).toString();
404
        if (type.isEmpty())
405
            type = "scxml";
406
        if (context->argumentCount() > 1)
407
            target = context->argument(1).toString();
408
        if (context->argumentCount() > 2)
409
            qScriptValueToSequence<QStringList>(context->argument(2),pnames);
410
        if (context->argumentCount() > 3) {
411
                QScriptValueIterator it (context->argument(3));
412
                while (it.hasNext()) {
413
                    it.next();
414
                    pvals.append(it.value().toVariant());
415
                } 
416
        } if (context->argumentCount() > 4)
417
                cnt = context->argument(4);
65e5705 by Noam Rosenthal at 2009-06-15 418
a6553f6 by Noam Rosenthal at 2009-06-07 419
        QScxmlInvokerFactory* invf = NULL;
420
        for (int i=0; i < scxml->pvt->invokerFactories.count() && invf == NULL; ++i)
421
            if (scxml->pvt->invokerFactories[i]->isTypeSupported(type))
422
                invf = scxml->pvt->invokerFactories[i];
423
        if (invf) {
424
            QScxmlEvent* ev = new QScxmlEvent("",pnames,pvals,cnt);
425
            ev->metaData.origin = scxml->baseUrl();
426
            ev->metaData.target = target;
427
            ev->metaData.targetType = type;
428
            ev->metaData.originType = "scxml";
429
            ev->metaData.kind = QScxmlEvent::MetaData::External;
430
            QScxmlInvoker* inv = invf->createInvoker(ev,scxml);
431
            if (inv)
432
                inv->activate();
433
            return engine->newQObject(inv);
434
        } else {
435
            scxml->postNamedEvent("error.invalidtargettype");
436
        }
437
438
    }
439
    return QScriptValue();
440
}
441
7a19605 by No'am Rosenthal at 2009-11-26 442
static QScriptValue dataAccess(QScriptContext *context, QScriptEngine *)
443
{
444
    if (context->argumentCount() == 0) {
445
        // getter
446
        return context->callee().property("value");
447
    } else if (context->argumentCount() == 1) {
448
        // setter
449
        QScriptValue val = context->argument(0);
450
        context->callee().setProperty("value",val);
451
        QScxml* scxml = qobject_cast<QScxml*>(context->callee().property("scxml").toQObject());
452
        if (scxml) {
453
            scxml->dataChanged(context->callee().property("key").toString(),val.toVariant());
454
        }
455
        return val;
456
    } else
457
        return QScriptValue();
458
}
a6553f6 by Noam Rosenthal at 2009-06-07 459
460
static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine)
461
{
7a41ce5 by Noam Rosenthal at 2009-06-08 462
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
a6553f6 by Noam Rosenthal at 2009-06-07 463
    if (scxml) {
464
        if (context->argumentCount() > 0) {
465
            QString name = context->argument(0).toString();
466
            if (!name.isEmpty()) {
467
                QSet<QAbstractState*> cfg = scxml->configuration();
468
                foreach (QAbstractState* st, cfg) {
469
                    if (st->objectName() == name)
470
                        return qScriptValueFromValue<bool>(engine,true);
471
                }
472
            }
473
        }
474
    }
475
    return qScriptValueFromValue<bool>(engine,false);
476
477
}
478
7a19605 by No'am Rosenthal at 2009-11-26 479
480
a6553f6 by Noam Rosenthal at 2009-06-07 481
};
084833d by No'am Rosenthal at 2009-07-13 482
483
void QScxmlPrivate::initScriptEngine(QScxml* thiz)
484
{
485
    QScriptValue glob = scriptEng->globalObject();
486
    QScriptValue scxmlObj = scriptEng->newQObject(thiz);
487
    glob.setProperty("In",scriptEng->newFunction(QScxmlFunctions::isInState));
488
    scxmlObj.setProperty("print",scriptEng->newFunction(QScxmlFunctions::script_print));
489
    scxmlObj.setProperty("postEvent",scriptEng->newFunction(QScxmlFunctions::postEvent));
490
    scxmlObj.setProperty("invoke",scriptEng->newFunction(QScxmlFunctions::invoke));
491
    scxmlObj.setProperty("cssTime",scriptEng->newFunction(QScxmlFunctions::cssTime));
492
    scxmlObj.setProperty("clone",scriptEng->newFunction(QScxmlFunctions::deepCopy));
493
    scxmlObj.setProperty("setTimeout",scriptEng->newFunction(QScxmlFunctions::setTimeout));
494
    scxmlObj.setProperty("clearTimeout",scriptEng->newFunction(QScxmlFunctions::clearTimeout));
495
    scxmlObj.setProperty("connectSignalToEvent",scriptEng->newFunction(QScxmlFunctions::connectSignalToEvent));
496
    QScriptValue dmObj = scriptEng->newObject();
7a19605 by No'am Rosenthal at 2009-11-26 497
    dataObj = scriptEng->newObject();
498
    dataObj.setProperty("_values",scriptEng->newObject());
499
    glob.setProperty("_data",dataObj);
084833d by No'am Rosenthal at 2009-07-13 500
    glob.setProperty("_global",scriptEng->globalObject());
501
    glob.setProperty("scxml",scxmlObj);
502
}
503
a6553f6 by Noam Rosenthal at 2009-06-07 504
/*!
505
  \class QScxmlEvent
506
  \brief The QScxmlEvent class stands for a general named event with a list of parameter names and parameter values.
507
508
  Encapsulates an event that conforms to the SCXML definition of events.
509
510
511
*/
512
/*! \enum QScxmlEvent::MetaData::Kind
513
514
    This enum specifies the kind (or context) of the event.
515
    \value Platform     An event coming from the     itself, such as a script error.
516
    \value Internal     An event sent with a <raise> or <send target="_internal">.
517
    \value External     An event sent from an invoker, directly from C++, or from a <send target="scxml"> element.
518
*/
519
520
/*!
521
  Returns the name of the event.
522
  */
523
  QString QScxmlEvent::eventName() const
524
{
525
    return ename;
526
}
527
  /*!
528
    Return a list containing the parameter names.
529
    */
530
QStringList QScxmlEvent::paramNames () const
531
{
532
    return pnames;
533
}
534
  /*!
535
    Return a list containing the parameter values.
536
    */
537
QVariantList QScxmlEvent::paramValues () const
538
{
539
    return pvals;
540
}
541
  /*!
542
    Return a QtScript object that can be passed as an additional parameter.
543
    */
544
QScriptValue QScxmlEvent::content () const
545
{
546
    return cnt;
547
}
548
  /*!
549
    Returns the parameter value equivalent to parameter \a name.
550
    */
551
QVariant QScxmlEvent::param (const QString & name) const
552
{
553
    int idx = pnames.indexOf(name);
554
    if (idx >= 0)
555
        return pvals[idx];
556
    else
557
        return QVariant();
558
}
559
/*!
560
  Creates a QScxmlEvent named \a name, with parameter names \a paramNames, parameter values \a paramValues, and
561
  a QtScript object \a content as an additional parameter.
562
*/
563
QScxmlEvent::QScxmlEvent(
564
        const QString & name,
565
        const QStringList & paramNames,
566
        const QVariantList & paramValues,
567
        const QScriptValue & content)
568
569
        : QEvent(QScxmlEvent::eventType()),ename(name),pnames(paramNames),pvals(paramValues),cnt(content)
570
{
571
    metaData.kind = MetaData::Internal;
572
}
573
574
/*! \class QScxmlTransition
575
  \brief The QScxmlTransition class stands for a transition that responds to QScxmlEvent, and can be made conditional with a \l conditionExpression.
576
  Equivalent to the SCXML transition tag.
577
578
  */
d06fc6d by Noam Rosenthal at 2009-06-08 579
  
580
a6553f6 by Noam Rosenthal at 2009-06-07 581
/*! \property QScxmlTransition::eventPrefix
582
  The event prefix to be used when testing if the transition needs to be invoked.
583
  Uses SCXML prefix matching. Use * to handle any event.
584
  */
585
/*! \property QScxmlTransition::conditionExpression
586
  A QtScript expression that's evaluated to test whether the transition needs to be invoked.
587
  */
588
589
/*!
590
  Creates a new QScxmlTransition from \a state, that uses \a machine to evaluate the conditions.
591
  */
592
QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine)
593
    : QAbstractTransition(state),scxml(machine)
594
{
595
}
7a19605 by No'am Rosenthal at 2009-11-26 596
597
void QScxmlTransition::setConditionExpression(const QString & c)
598
{
599
    prog = QScriptProgram(c,scxml->baseUrl().toLocalFile());
600
}
601
602
QString QScxmlTransition::conditionExpression() const
603
{
604
    return prog.sourceCode();
605
}
6a9f40c by Noam Rosenthal at 2009-06-18 606
/*!
607
   \reimp
608
*/
609
void QScxmlTransition::onTransition(QEvent*)
610
{
611
}
a6553f6 by Noam Rosenthal at 2009-06-07 612
/*!
613
  \internal
614
  */
d0441f6 by Noam Rosenthal at 2009-06-08 615
bool QScxmlTransition::eventTest(QEvent *e)
a6553f6 by Noam Rosenthal at 2009-06-07 616
{
617
    QScriptEngine* engine = scxml->scriptEngine();
7a19605 by No'am Rosenthal at 2009-11-26 618
    QString event;
a6553f6 by Noam Rosenthal at 2009-06-07 619
620
    if (e) {
9be5070 by Marc Schmitzer at 2012-01-27 621
        bool found = false;
622
        if (e->type() == QEvent::None) {
623
            // QEvent::None events are posted when a state is entered.
624
            // If we have no event prefixes we are triggered by those events.
625
            found = ev.isEmpty();
626
        }
627
        else if (e->type() == QScxmlEvent::eventType()) {
7a19605 by No'am Rosenthal at 2009-11-26 628
            event = ((QScxmlEvent*)e)->eventName();
629
        }
630
        for (int i=0; i < ev.size() && !found; ++i) {
631
            QString prefix = ev[i];
632
            found = prefix=="*" || prefix==event || event.startsWith(prefix)
633
                     || (prefix.endsWith(".*") && event.startsWith(prefix.left(prefix.indexOf(".*"))))
634
                     ;
a6553f6 by Noam Rosenthal at 2009-06-07 635
        }
7a19605 by No'am Rosenthal at 2009-11-26 636
        if (!found  )
a6553f6 by Noam Rosenthal at 2009-06-07 637
            return false;
638
    }
639
640
641
    if (!conditionExpression().isEmpty()) {
7a19605 by No'am Rosenthal at 2009-11-26 642
        QScriptValue v = engine->evaluate(prog);
a6553f6 by Noam Rosenthal at 2009-06-07 643
        if (engine->hasUncaughtException()) {
644
645
            QScxmlEvent* e = new QScxmlEvent("error.illegalcond",
646
                                         QStringList()<< "error" << "expr" << "line" << "backtrace",
647
                                         QVariantList()
648
                                            << QVariant(engine->uncaughtException().toString())
649
                                            << QVariant(conditionExpression())
650
                                            << QVariant(engine->uncaughtExceptionLineNumber())
651
                                            << QVariant(engine->uncaughtExceptionBacktrace()));
7a19605 by No'am Rosenthal at 2009-11-26 652
            qDebug() << engine->uncaughtException().toString() << prog.sourceCode();
a6553f6 by Noam Rosenthal at 2009-06-07 653
            e->metaData.kind = QScxmlEvent::MetaData::Platform;
654
            scxml->postEvent(e);
655
            engine->clearExceptions();
656
            return false;
657
        }
658
        return v.toBoolean();
659
    }
660
661
    return true;
662
}
663
664
class QScxmlDefaultInvoker : public QScxmlInvoker
665
{
666
    Q_OBJECT
7a41ce5 by Noam Rosenthal at 2009-06-08 667
    
a6553f6 by Noam Rosenthal at 2009-06-07 668
669
    public:
670
    QScxmlDefaultInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p),cancelled(false),childSm(0)
671
    {
672
        childSm = QScxml::load (ievent->metaData.origin.resolved(ievent->metaData.target).toLocalFile(),this);
673
        if (childSm == NULL) {
674
            postParentEvent("error.targetunavailable");
675
        } else {
676
           connect(childSm,SIGNAL(finished()),this,SLOT(deleteLater()));
677
678
        }
679
    }
7a41ce5 by Noam Rosenthal at 2009-06-08 680
    
681
    
a6553f6 by Noam Rosenthal at 2009-06-07 682
683
    static void initInvokerFactory(QScxml*) {}
684
685
    static bool isTypeSupported(const QString & t) { return t.isEmpty() || t.toLower() == "scxml"; }
686
687
    public Q_SLOTS:
688
    void activate ()
689
    {
690
        if (childSm)
691
            childSm->start();
692
    }
693
694
    void cancel ()
695
    {
7a41ce5 by Noam Rosenthal at 2009-06-08 696
        cancelled = true;
a6553f6 by Noam Rosenthal at 2009-06-07 697
        if (childSm)
698
            childSm->stop();
699
700
    }
7a41ce5 by Noam Rosenthal at 2009-06-08 701
    
a6553f6 by Noam Rosenthal at 2009-06-07 702
    private:
7a41ce5 by Noam Rosenthal at 2009-06-08 703
        bool cancelled;
a6553f6 by Noam Rosenthal at 2009-06-07 704
        QScxml* childSm;
705
};
706
class QScxmlBindingInvoker : public QScxmlInvoker
707
{
708
    Q_OBJECT
709
    QScriptValue content;
710
    QScriptValue stored;
711
712
    public:
713
    QScxmlBindingInvoker(QScxmlEvent* ievent, QScxml* p) : QScxmlInvoker(ievent,p)
714
    {
715
    }
716
717
    static void initInvokerFactory(QScxml*) {}
718
719
    static bool isTypeSupported(const QString & t) { return t.toLower() == "q-bindings"; }
720
721
    public Q_SLOTS:
722
    void activate ()
723
    {
724
        QScriptEngine* engine = ((QScxml*)parent())->scriptEngine();
725
        QScriptValue content = initEvent->content();
726
        if (content.isArray()) {
727
            stored = content.engine()->newArray(content.property("length").toInt32());
728
729
            QScriptValueIterator it (content);
730
            for (int i=0; it.hasNext(); ++i) {
731
                it.next();
732
                if (it.value().isArray()) {
733
                    QScriptValue object = it.value().property(0);
734
                    QString property = it.value().property(1).toString();
735
                    QScriptValue val = it.value().property(2);
736
                    QScriptValue arr = engine->newArray(3);
737
                    arr.setProperty("0",it.value().property(0));
738
                    arr.setProperty("1",it.value().property(1));
739
                    if (object.isQObject()) {
740
                        QObject* o = object.toQObject();
741
                        arr.setProperty("2",engine->newVariant(o->property(property.toAscii().constData())));
742
                        o->setProperty(property.toAscii().constData(),val.toVariant());
743
                    } else if (object.isObject()) {
744
                        arr.setProperty("2",object.property(property));
745
                        object.setProperty(property,val);
746
                    }
747
                    stored.setProperty(i,arr);
748
                }
749
            }
750
        }
751
    }
752
753
    void cancel ()
754
    {
755
        if (stored.isArray()) {
756
            QScriptValueIterator it (stored);
757
            while (it.hasNext()) {
758
                it.next();
759
                if (it.value().isArray()) {
760
                    QScriptValue object = it.value().property(0);
761
                    QString property = it.value().property(1).toString();
762
                    QScriptValue val = it.value().property(2);
763
                    if (object.isQObject()) {
764
                        QObject* o = object.toQObject();
765
                        o->setProperty(property.toAscii().constData(),val.toVariant());
766
                    } else if (object.isObject()) {
767
                        object.setProperty(property,val);
768
                    }
769
                }
770
            }
771
        }
772
    }
773
};
d06fc6d by Noam Rosenthal at 2009-06-08 774
a6553f6 by Noam Rosenthal at 2009-06-07 775
  
776
/*!
777
\fn QScxmlInvoker::~QScxmlInvoker()
778
*/
779
780
781
/*!
782
\fn QScxml::eventTriggered(const QString & name)
783
784
This signal is emitted when external event \a name is handled in the state machine. 
785
*/
786
084833d by No'am Rosenthal at 2009-07-13 787
void QScxml::init()
a6553f6 by Noam Rosenthal at 2009-06-07 788
{
789
    static QScxmlAutoInvokerFactory<QScxmlDefaultInvoker> _s_defaultInvokerFactory;
790
    static QScxmlAutoInvokerFactory<QScxmlBindingInvoker> _s_bindingInvokerFactory;
791
    registerInvokerFactory(&_s_defaultInvokerFactory);
792
    registerInvokerFactory(&_s_bindingInvokerFactory);
793
    connect(this,SIGNAL(started()),this,SLOT(registerSession()));
794
    connect(this,SIGNAL(stopped()),this,SLOT(unregisterSession()));
084833d by No'am Rosenthal at 2009-07-13 795
    pvt->initScriptEngine(this);
a6553f6 by Noam Rosenthal at 2009-06-07 796
}
084833d by No'am Rosenthal at 2009-07-13 797
/*!
798
  Creates a new QScxml object, with parent \a parent.
799
  */
800
801
QScxml::QScxml(QObject* parent)
802
    : QStateMachine(parent)
803
{
804
    pvt = new QScxmlPrivate;
805
    pvt->scriptEng = new QScriptEngine(this);
806
    init();
807
808
}
809
/*!
810
  Creates a new QScxml object, with parent \a parent. The state machine will operate on script-engine \a eng.
811
  */
812
813
QScxml::QScxml(QScriptEngine* eng, QObject* parent)
814
    : QStateMachine(parent)
815
{
816
    pvt = new QScxmlPrivate;
817
    pvt->scriptEng = eng;
818
    init();
819
}
a6553f6 by Noam Rosenthal at 2009-06-07 820
821
/*! \internal */
822
void QScxml::beginSelectTransitions(QEvent* ev)
823
{
824
    QScriptValue eventObj = pvt->scriptEng->newObject();
825
    if (ev) {
826
        if (ev->type() == QScxmlEvent::eventType()) {
827
            QScxmlEvent* se = (QScxmlEvent*)ev;
828
            eventObj.setProperty("name",qScriptValueFromValue<QString>(pvt->scriptEng,se->eventName()));
6a9f40c by Noam Rosenthal at 2009-06-18 829
            eventObj.setProperty("target",QScriptValue(se->metaData.target.toString()));
a6553f6 by Noam Rosenthal at 2009-06-07 830
            eventObj.setProperty("targettype",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.targetType));
831
            eventObj.setProperty("invokeid",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.invokeID));
6a9f40c by Noam Rosenthal at 2009-06-18 832
            eventObj.setProperty("origin",QScriptValue(se->metaData.origin.toString()));
a6553f6 by Noam Rosenthal at 2009-06-07 833
            eventObj.setProperty("originType",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.originType));
834
            switch (se->metaData.kind) {
835
                case QScxmlEvent::MetaData::Internal:
836
                    eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "internal"));
837
                break;
838
                case QScxmlEvent::MetaData::External:
839
                    eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "external"));
840
                break;
841
                case QScxmlEvent::MetaData::Platform:
842
                    eventObj.setProperty("kind",qScriptValueFromValue<QString>(pvt->scriptEng, "platform"));
843
                default:
844
                break;
845
846
            }
847
848
            QScriptValue dataObj = pvt->scriptEng->newObject();
849
            int i=0;
850
            foreach (QString s, se->paramNames()) {
851
                QScriptValue v = qScriptValueFromValue(pvt->scriptEng, se->paramValues()[i]);
852
                dataObj.setProperty(QString::number(i),v);
853
                dataObj.setProperty(s,v);
854
                ++i;
855
            }
856
            eventObj.setProperty("data",dataObj);
857
            emit eventTriggered(se->eventName());
858
        }
859
    }
860
    scriptEngine()->globalObject().setProperty("_event",eventObj);
861
862
    QHash<QString,QAbstractState*> curTargets;
863
}
864
65e5705 by Noam Rosenthal at 2009-06-15 865
static QString _q_configToString (QAbstractState* from,int level, const QSet<QAbstractState*> & config)
866
{
867
    QString str;
868
    if (from) {
869
        if (level >= 0) {
870
            for (int i=0; i < level; ++i)
871
                str += "\t";
872
873
            QState* p = qobject_cast<QState*>(from->parent());
874
            char c = '$';
875
            if (qobject_cast<QHistoryState*>(from))
876
                c = '^';
877
            else if (qobject_cast<QFinalState*>(from))
878
                c = '~';
879
            else if (p) {
880
             if (p->childMode() == QState::ParallelStates)
881
                c = '{';
882
            }
883
            str += QString("%1%2 %3\n").arg(config.contains(from)?">":" ").arg(c).arg(from->objectName());
884
        }
885
        QObjectList ch = from->children();
886
        foreach (QObject* o, ch)
887
            str += _q_configToString(qobject_cast<QAbstractState*>(o),level+1,config);
888
    }
889
    
890
    return str;
891
}
892
a6553f6 by Noam Rosenthal at 2009-06-07 893
/*! \internal */
65e5705 by Noam Rosenthal at 2009-06-15 894
895
084833d by No'am Rosenthal at 2009-07-13 896
void QScxml::endMicrostep(QEvent*)
a6553f6 by Noam Rosenthal at 2009-06-07 897
{
898
    scriptEngine()->globalObject().setProperty("_event",QScriptValue());
899
7a19605 by No'am Rosenthal at 2009-11-26 900
    emit configurationChanged();
a6553f6 by Noam Rosenthal at 2009-06-07 901
}
902
903
/*! Returns the script engine attached to the state-machine. */
904
QScriptEngine* QScxml::scriptEngine () const
905
{
906
    return pvt->scriptEng;
907
}
908
909
/*!
910
    Registers object \a o to the script engine attached to the state machine.
911
    The object can be accessible from global variable \a name. If \a name is not provided,
912
    the object's name is used. If \a recursive is true, all the object's decendants are registered
913
    as global objects, with their respective object names as variable names.
914
*/
915
void QScxml::registerObject (QObject* o, const QString & name, bool recursive)
916
{
917
    QString n(name);
918
    if (n.isEmpty())
919
        n = o->objectName();
920
    if (!n.isEmpty())
921
        pvt->scriptEng->globalObject().setProperty(n,pvt->scriptEng->newQObject(o));
922
    if (recursive) {
923
        QObjectList ol = o->findChildren<QObject*>();
924
        foreach (QObject* oo, ol) {
925
            if (!oo->objectName().isEmpty())
926
                registerObject(oo);
927
        }
928
    }
929
}
930
931
/*!
932
  Posts a QScxmlEvent named \a event, with no payload.
933
  \sa QScxmlEvent
934
  */
935
void QScxml::postNamedEvent(const QString & event)
936
{
937
    QScxmlEvent* e = new QScxmlEvent(event);
938
    e->metaData.kind = QScxmlEvent::MetaData::External;
939
    postEvent(e);
940
}
7a19605 by No'am Rosenthal at 2009-11-26 941
942
void QScxml::setData(const QString & id, const QVariant & val)
943
{
944
    QScriptValue accessor = pvt->dataObj.property(id);
945
    if (accessor.isFunction()) {
946
        accessor.setProperty("value",scriptEngine()->newVariant(val));
947
    }
948
}
949
a6553f6 by Noam Rosenthal at 2009-06-07 950
/*!
951
    Executes script \a s in the attached script engine.
952
    If the script fails, a "error.illegalvalue" event is posted to the state machine.
953
*/
954
7a19605 by No'am Rosenthal at 2009-11-26 955
void QScxml::executeScript (const QScriptProgram & s)
a6553f6 by Noam Rosenthal at 2009-06-07 956
{
9a50c13 by No'am Rosenthal at 2009-11-27 957
//    qDebug() << "Executing\n--------------------------\n"<<s.sourceCode();
7a19605 by No'am Rosenthal at 2009-11-26 958
        pvt->scriptEng->evaluate (s);
a6553f6 by Noam Rosenthal at 2009-06-07 959
        if (pvt->scriptEng->hasUncaughtException()) {
960
            QScxmlEvent* e = new QScxmlEvent("error.illegalvalue",
961
                                         QStringList()<< "error" << "expr" << "line" << "backtrace",
962
                                         QVariantList()
963
                                            << QVariant(pvt->scriptEng->uncaughtException().toString())
7a19605 by No'am Rosenthal at 2009-11-26 964
                                            << QVariant(s.sourceCode())
a6553f6 by Noam Rosenthal at 2009-06-07 965
                                            << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber())
966
                                            << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace()));
967
            e->metaData.kind = QScxmlEvent::MetaData::Platform;
7a19605 by No'am Rosenthal at 2009-11-26 968
            qDebug() << pvt->scriptEng->uncaughtException().toString() << s.sourceCode();
a6553f6 by Noam Rosenthal at 2009-06-07 969
            postEvent(e);
970
            pvt->scriptEng->clearExceptions();
971
        }
7a41ce5 by Noam Rosenthal at 2009-06-08 972
//    qDebug() <<"\n--------------------\n";
a6553f6 by Noam Rosenthal at 2009-06-07 973
}
7a19605 by No'am Rosenthal at 2009-11-26 974
void QScxml::executeScript (const QString & s)
975
{
976
    executeScript(QScriptProgram(s,baseUrl().toLocalFile()));
977
}
a6553f6 by Noam Rosenthal at 2009-06-07 978
979
/*!
980
  Enabled invoker factory \a f to be called from <invoke /> tags.
981
  */
982
983
void QScxml::registerInvokerFactory (QScxmlInvokerFactory* f)
984
{
985
    pvt->invokerFactories << f;
986
    f->init(this);
987
}
988
989
/*! \class QScxmlInvoker
990
    \brief The QScxmlInvoker class an invoker, which the state-machine context can activate or cancel
991
        with an <invoke> tag.
992
993
994
    An invoker is a object that represents an external component that the state machine
995
    can activate when the encompassing state is entered, or cancel when the encompassing
996
    state is exited from.
997
  */
998
999
/*! \fn QScxmlInvoker::QScxmlInvoker(QScxmlEvent* ievent, QStateMachine* parent)
1000
    When reimplementing the constructor, always use the two parameters (\a ievent and \a parent),
1001
    as they're called from QScxmlInvokerFactory.
1002
*/
1003
1004
/*! \fn  QScxmlInvoker::activate() 
1005
    This function is called when the encompassing state is entered.
1006
    The call to this function from the state-machine context is asynchronous, to make sure
1007
    that the state is not exited during the same step in which it's entered.
1008
1009
*/
1010
1011
/*! \fn QScxmlInvoker::cancel()
1012
    Reimplement this function to allow for asynchronous cancellation of the invoker.
1013
    It's the invoker's responsibility to delete itself after this function has been called.
1014
    The default implementation deletes the invoker.
1015
*/
1016
1017
/*! \fn QScxml* QScxmlInvoker::parentStateMachine()
1018
  Returns the state machine encompassing the invoker.
1019
  */
1020
1021
/*!
1022
  Posts an event \a e to the state machine encompassing the invoker.
1023
  */
1024
void QScxmlInvoker::postParentEvent (QScxmlEvent* e)
1025
{
1026
    e->metaData.origin = initEvent->metaData.target;
1027
    e->metaData.target = initEvent->metaData.origin;
1028
    e->metaData.originType = initEvent->metaData.targetType;
1029
    e->metaData.targetType = initEvent->metaData.originType;
1030
    e->metaData.kind = QScxmlEvent::MetaData::External;
1031
    e->metaData.invokeID = initEvent->metaData.invokeID;
1032
    parentStateMachine()->postEvent(e);
1033
}
1034
/*! \overload
1035
  Posts a QScxmlEvent named \a e to the encompassing state machine.
1036
  */
1037
void QScxmlInvoker::postParentEvent(const QString & e)
1038
{
1039
    QScxmlEvent* ev = new QScxmlEvent(e);
1040
    ev->metaData.kind = QScxmlEvent::MetaData::External;
1041
    postParentEvent(ev);
1042
}
1043
/*! \internal */
1044
QScxml::~QScxml()
1045
{
1046
    delete pvt;
1047
}
d06fc6d by Noam Rosenthal at 2009-06-08 1048
/*! 
1049
returns the id for this invoker 
1050
*/
d0441f6 by Noam Rosenthal at 2009-06-08 1051
QString QScxmlInvoker::id () const
a6553f6 by Noam Rosenthal at 2009-06-07 1052
{
7a41ce5 by Noam Rosenthal at 2009-06-08 1053
    return initEvent->metaData.invokeID;
a6553f6 by Noam Rosenthal at 2009-06-07 1054
}
1055
void QScxmlInvoker::setID(const QString & id)
1056
{
7a41ce5 by Noam Rosenthal at 2009-06-08 1057
    initEvent->metaData.invokeID = id;
a6553f6 by Noam Rosenthal at 2009-06-07 1058
}
1059
1060
QScxmlInvoker::~QScxmlInvoker()
1061
{
7a41ce5 by Noam Rosenthal at 2009-06-08 1062
    if (cancelled)
1063
        postParentEvent("CancelResponse");
1064
    else
1065
        postParentEvent(QString("done.invoke.%1").arg(initEvent->metaData.invokeID));
a6553f6 by Noam Rosenthal at 2009-06-07 1066
}
1067
/*!
1068
    \property QScxml::baseUrl
1069
    The url used to resolve scripts and invoke urls.
1070
*/
1071
QUrl QScxml::baseUrl() const
1072
{
1073
    return pvt->burl;
1074
}
1075
1076
void QScxml::setBaseUrl(const QUrl & u)
1077
{
1078
    pvt->burl = u;
1079
}
1080
7a19605 by No'am Rosenthal at 2009-11-26 1081
QStringList QScxml::knownEventNames() const
1082
{
1083
    return pvt->knownEvents.toList();
1084
}
1085
a6553f6 by Noam Rosenthal at 2009-06-07 1086
void QScxml::registerSession()
1087
{
1088
    pvt->sessionID = QUuid::createUuid().toString();
1089
    pvt->sessions[pvt->sessionID] = this;
1090
    pvt->scriptEng->globalObject().setProperty("_sessionid",qScriptValueFromValue<QString>(scriptEngine(), pvt->sessionID));
1091
    executeScript(pvt->startScript);
1092
}
1093
1094
void QScxml::unregisterSession()
1095
{
1096
    pvt->scriptEng->globalObject().setProperty("_sessionid",QScriptValue());
1097
    pvt->sessions.remove(pvt->sessionID);
1098
}
1099
1100
/*!
1101
    Returns a statically-generated event type to be used by SCXML events.
1102
*/
1103
QEvent::Type QScxmlEvent::eventType()
1104
{
1105
    static QEvent::Type _t = (QEvent::Type)QEvent::registerEventType(QEvent::User+200);
1106
    return _t;
1107
}
1108
const char SCXML_NAMESPACE [] = "http://www.w3.org/2005/07/scxml";
1109
1110
1111
1112
struct ScTransitionInfo
1113
{
1114
1115
    QScxmlTransition* transition;
1116
    QStringList targets;
1117
    QString script;
d0441f6 by Noam Rosenthal at 2009-06-08 1118
    ScTransitionInfo() : transition(NULL) {}
1119
};
1120
1121
class QScxmlScriptExec : public QObject
1122
{
7a41ce5 by Noam Rosenthal at 2009-06-08 1123
    Q_OBJECT
7a19605 by No'am Rosenthal at 2009-11-26 1124
    QScriptProgram prog;
7a41ce5 by Noam Rosenthal at 2009-06-08 1125
    QScxml* scxml;
1126
    public:
7a19605 by No'am Rosenthal at 2009-11-26 1127
        QScxmlScriptExec(const QString & scr, QScxml* scx) :
1128
                prog(QScriptProgram(scr,scx->baseUrl().toLocalFile())),scxml(scx)
7a41ce5 by Noam Rosenthal at 2009-06-08 1129
        {
1130
        }
1131
    public Q_SLOTS:
1132
        void exec()
1133
        {
7a19605 by No'am Rosenthal at 2009-11-26 1134
            scxml->executeScript(prog);
7a41ce5 by Noam Rosenthal at 2009-06-08 1135
        }
a6553f6 by Noam Rosenthal at 2009-06-07 1136
};
1137
1138
struct ScStateInfo
1139
{
1140
    QString initial;
1141
};
1142
1143
struct ScHistoryInfo
1144
{
1145
    QHistoryState* hstate;
1146
    QString defaultStateID;
1147
};
1148
1149
struct ScExecContext
1150
{
1151
    QScxml* sm;
1152
    QString script;
1153
    enum {None, StateEntry,StateExit,Transition } type;
1154
    QScxmlTransition* trans;
1155
    QAbstractState* state;
1156
    ScExecContext() : sm(NULL),type(None),trans(NULL),state(NULL)
1157
    {
1158
    }
1159
1160
    void applyScript()
1161
    {
1162
        if (!script.isEmpty()) {
1163
            QScxmlScriptExec* exec = new QScxmlScriptExec(script,sm);
1164
            switch(type) {
1165
                case StateEntry:
d0441f6 by Noam Rosenthal at 2009-06-08 1166
                    QObject::connect(state,SIGNAL(entered()),exec,SLOT(exec()));
a6553f6 by Noam Rosenthal at 2009-06-07 1167
                break;
1168
                case StateExit:
d0441f6 by Noam Rosenthal at 2009-06-08 1169
                    QObject::connect(state,SIGNAL(exited()),exec,SLOT(exec()));
a6553f6 by Noam Rosenthal at 2009-06-07 1170
                break;
1171
                case Transition:
6a9f40c by Noam Rosenthal at 2009-06-18 1172
                    QObject::connect(trans,SIGNAL(triggered()),exec,SLOT(exec()));
a6553f6 by Noam Rosenthal at 2009-06-07 1173
                break;
1174
                default:
1175
                delete exec;
1176
                break;
1177
            }
1178
        }
1179
    }
1180
};
1181
1182
class QScxmlLoader
1183
{
1184
    public:
1185
    QScxml* stateMachine;
1186
1187
    QList<ScTransitionInfo> transitions;
1188
    QHash<QState*,ScStateInfo> stateInfo;
1189
    QList<ScHistoryInfo> historyInfo;
1190
    QHash<QString,QAbstractState*> stateByID;
1191
    QSet<QString> signalEvents;
7a41ce5 by Noam Rosenthal at 2009-06-08 1192
     QSet<QState*> statesWithFinal;
d0441f6 by Noam Rosenthal at 2009-06-08 1193
   void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename);
a6553f6 by Noam Rosenthal at 2009-06-07 1194
    QScxml* load (QIODevice* device, QObject* obj = NULL, const QString & filename = "");
1195
1196
    QScriptValue evaluateFile (const QString & fn)
1197
    {
1198
        QFile f (fn);
1199
        f.open(QIODevice::ReadOnly);
1200
        return stateMachine->scriptEngine()->evaluate(QString::fromUtf8(f.readAll()),fn);
1201
    }
1202
};
1203
1204
1205
d0441f6 by Noam Rosenthal at 2009-06-08 1206
void QScxmlLoader::loadState (
a6553f6 by Noam Rosenthal at 2009-06-07 1207
        QState* stateParam,
1208
        QIODevice *dev,
1209
        const QString & stateID,
1210
        const QString & filename)
1211
{
1212
    QXmlStreamReader r (dev);
1213
    QState* curState = NULL;
1214
    ScExecContext curExecContext;
1215
    curExecContext.sm = stateMachine;
1216
    QState* topLevelState = NULL;
1217
    QHistoryState* curHistoryState = NULL;
1218
    QString initialID = "";
65e5705 by Noam Rosenthal at 2009-06-15 1219
    QString idLocation, target, targetType, eventName, delay, content;
1220
    QStringList paramNames, paramVals;
a6553f6 by Noam Rosenthal at 2009-06-07 1221
    QScxmlTransition* curTransition = NULL;
1222
    bool inRoot = true;
1223
    while (!r.atEnd()) {
1224
        r.readNext();
1225
        if (r.hasError()) {
1226
            qDebug() << QString("SCXML read error at line %1, column %2: %3").arg(r.lineNumber()).arg(r. columnNumber()).arg(r.errorString());
1227
            return;
1228
        }
1229
        if (r.namespaceUri() == SCXML_NAMESPACE || r.namespaceUri() == "") {
1230
            if (r.isStartElement()) {
1231
                if (r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) {
1232
                    if (stateID == "") {
1233
                        topLevelState = curState = stateParam;
1234
                        stateInfo[curState].initial = r.attributes().value("initial").toString();
7092b92 by No'am Rosenthal at 2009-07-23 1235
                        if (curState == stateMachine) {
a6553f6 by Noam Rosenthal at 2009-06-07 1236
                            stateMachine->scriptEngine()->globalObject().setProperty("_name",qScriptValueFromValue<QString>(stateMachine->scriptEngine(),r.attributes().value("name").toString()));
1237
                        }
1238
1239
                    }
1240
                } else if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) {
1241
                    inRoot = false;
1242
                    QString id = r.attributes().value("id").toString();
1243
                    QState* newState = NULL;
1244
                    if (curState) {
1245
                        newState= new QState(r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0 ? QState::ParallelStates : QState::ExclusiveStates,
1246
                                                        curState);
1247
                    } else if (id == stateID) {
1248
                        topLevelState = newState = stateParam;
1249
1250
                    }
1251
                    if (newState) {
1252
                        stateInfo[newState].initial = r.attributes().value("initial").toString();
1253
                        newState->setObjectName(id);
1254
                        if (!id.isEmpty() && stateInfo[curState].initial == id) {
1255
7092b92 by No'am Rosenthal at 2009-07-23 1256
                            if (curState == stateMachine)
a6553f6 by Noam Rosenthal at 2009-06-07 1257
                                stateMachine->setInitialState(newState);
1258
                            else
1259
                                curState->setInitialState(newState);
1260
                        }
1261
                        QString src = r.attributes().value("src").toString();
1262
                        if (!src.isEmpty()) {
1263
                            int refidx = src.indexOf('#');
1264
                            QString srcfile, refid;
1265
                            if (refidx > 0) {
1266
                                srcfile = src.left(refidx);
1267
                                refid = src.mid(refidx+1);
1268
                            } else
1269
                                srcfile = src;
1270
                            srcfile = QDir::cleanPath( QFileInfo(filename).dir().absoluteFilePath(srcfile));
1271
                            QFile newFile (srcfile);
1272
                            if (newFile.exists()) {
1273
                                newFile.open(QIODevice::ReadOnly);
1274
                                loadState(newState,&newFile,refid,srcfile);
1275
                            }
1276
                        }
1277
                        initialID = r.attributes().value("initial").toString();
1278
                        stateByID[id] = newState;
1279
                        curState = newState;
1280
                        curExecContext.state = newState;
1281
                    }
1282
1283
                } else if (r.name().toString().compare("initial",Qt::CaseInsensitive) == 0) {
1284
                    if (curState && stateInfo[curState].initial == "") {
1285
                        QState* newState = new QState(curState);
1286
                        curState->setInitialState(newState);
1287
                    }
1288
                } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) {
1289
                    if (curState) {
1290
                        QString id = r.attributes().value("id").toString();
d0441f6 by Noam Rosenthal at 2009-06-08 1291
                        curHistoryState = new QHistoryState(r.attributes().value("type") == "shallow" ? QHistoryState::ShallowHistory : QHistoryState::DeepHistory,curState);
a6553f6 by Noam Rosenthal at 2009-06-07 1292
                        curHistoryState->setObjectName(id);
1293
                        stateByID[id] = curHistoryState;
1294
                    }
1295
                } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) {
1296
                    if (curState) {
1297
                        QString id = r.attributes().value("id").toString();
d0441f6 by Noam Rosenthal at 2009-06-08 1298
                        QFinalState* f = new QFinalState(curState);
a6553f6 by Noam Rosenthal at 2009-06-07 1299
                        f->setObjectName(id);
1300
                        curExecContext.state = f;
7a41ce5 by Noam Rosenthal at 2009-06-08 1301
                        statesWithFinal.insert(curState);
1302
                        QState* gp = qobject_cast<QState*>(curState->parentState());
1303
                        if (gp) {
1304
                            if (gp->childMode() == QState::ParallelStates) {
1305
                                statesWithFinal.insert(gp);
1306
                            }
1307
                        }
a6553f6 by Noam Rosenthal at 2009-06-07 1308
                        stateByID[id] = f;
1309
                    }
1310
                } else if (r.name().toString().compare("script",Qt::CaseInsensitive) == 0) {
1311
                    QString txt = r.readElementText().trimmed();
1312
                    if (curExecContext.type == ScExecContext::None && curState == topLevelState) {
7a19605 by No'am Rosenthal at 2009-11-26 1313
                        stateMachine->executeScript(QScriptProgram(txt,stateMachine->baseUrl().toLocalFile()));
a6553f6 by Noam Rosenthal at 2009-06-07 1314
                    } else
1315
                        curExecContext.script += txt;
1316
                } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) {
1317
                    curExecContext.script +=
65e5705 by Noam Rosenthal at 2009-06-15 1318
                            QString("scxml.print('[' + %1 + '][' + %2 + ']' + %3);")
9a50c13 by No'am Rosenthal at 2009-11-27 1319
                            .arg(r.attributes().value("label").toString())
1320
                            .arg(r.attributes().value("level").toString())
1321
                            .arg(r.attributes().value("expr").toString());
a6553f6 by Noam Rosenthal at 2009-06-07 1322
1323
                } else if (r.name().toString().compare("assign",Qt::CaseInsensitive) == 0) {
1324
                    QString locattr = r.attributes().value("location").toString();
1325
                    if (locattr.isEmpty()) {
1326
                        locattr = r.attributes().value("dataid").toString();
1327
                        if (!locattr.isEmpty())
1328
                            locattr = "_data." + locattr;
1329
                    }
1330
                    if (!locattr.isEmpty()) {
9a50c13 by No'am Rosenthal at 2009-11-27 1331
                        curExecContext.script += QString ("%1 = %2;").arg(locattr).arg(r.attributes().value("expr").toString());
a6553f6 by Noam Rosenthal at 2009-06-07 1332
                    }
1333
                } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) {
9a50c13 by No'am Rosenthal at 2009-11-27 1334
                    curExecContext.script += QString("if (%1) {").arg(r.attributes().value("cond").toString());
a6553f6 by Noam Rosenthal at 2009-06-07 1335
                } else if (r.name().toString().compare("elseif",Qt::CaseInsensitive) == 0) {
9a50c13 by No'am Rosenthal at 2009-11-27 1336
                    curExecContext.script += QString("} elseif (%1) {").arg(r.attributes().value("cond").toString());
a6553f6 by Noam Rosenthal at 2009-06-07 1337
                } else if (r.name().toString().compare("else",Qt::CaseInsensitive) == 0) {
1338
                    curExecContext.script += " } else { ";
1339
                } else if (r.name().toString().compare("cancel",Qt::CaseInsensitive) == 0) {
9a50c13 by No'am Rosenthal at 2009-11-27 1340
                    curExecContext.script += QString("scxml.clearTimeout (%1);").arg(r.attributes().value("id").toString());
a6553f6 by Noam Rosenthal at 2009-06-07 1341
                } else if (r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0) {
1342
                    curExecContext.type = ScExecContext::StateEntry;
1343
                    curExecContext.script = "";
1344
                } else if (r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0) {
1345
                    curExecContext.type = ScExecContext::StateExit;
1346
                    curExecContext.script = "";
1347
                } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0 || r.name().toString().compare("event",Qt::CaseInsensitive) == 0 ) {
7a19605 by No'am Rosenthal at 2009-11-26 1348
                    eventName = r.attributes().value("event").toString();
1349
                    stateMachine->pvt->knownEvents.insert(eventName);
1350
                    eventName = QString("\"%1\"").arg(eventName);
65e5705 by Noam Rosenthal at 2009-06-15 1351
                    target = "'_internal'";
1352
                    targetType = "scxml";
1353
                    content = "{}";
1354
                    paramNames.clear();
1355
                    paramVals.clear();
a6553f6 by Noam Rosenthal at 2009-06-07 1356
                } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) {
65e5705 by Noam Rosenthal at 2009-06-15 1357
                    paramNames.clear ();
1358
                    paramVals.clear();
1359
                    content = "{}";
1360
1361
                    target = r.attributes().value("target").toString();
7a19605 by No'am Rosenthal at 2009-11-26 1362
                    if (target == "") {
1363
                        target = r.attributes().value("targetexpr").toString();
1364
                        if (target == "")
1365
                            target = "\"\"";
1366
                    } else
1367
                        target = "\""+target+"\"";
65e5705 by Noam Rosenthal at 2009-06-15 1368
                    targetType = r.attributes().value("type").toString();
1369
                    eventName = r.attributes().value("event").toString();
7a19605 by No'am Rosenthal at 2009-11-26 1370
                    if (eventName == "") {
1371
                        eventName = r.attributes().value("eventexpr").toString();
1372
                    } else {
1373
                        stateMachine->pvt->knownEvents.insert(eventName);
1374
                        eventName = "'"+eventName+"'";
1375
                    }
65e5705 by Noam Rosenthal at 2009-06-15 1376
                    QStringList nameList = r.attributes().value("namelist").toString().split(" ");
1377
                    foreach (QString name,nameList) {
1378
                        if (name != "") {
1379
                            paramNames << name;
1380
                            paramVals << QString("_data.") + name;
1381
                        }
1382
                    }
a6553f6 by Noam Rosenthal at 2009-06-07 1383
                    idLocation = r.attributes().value("idlocation").toString();
1384
                    if (idLocation.isEmpty())
1385
                        idLocation = r.attributes().value("sendid").toString();
65e5705 by Noam Rosenthal at 2009-06-15 1386
                        
1387
                    delay = r.attributes().value("delay").toString();
7a19605 by No'am Rosenthal at 2009-11-26 1388
                    if (delay == "") {
1389
                        delay = r.attributes().value("delayexpr").toString();
1390
                        if (delay == "")
1391
                            delay = "0";
1392
                    } else
1393
                        delay = "'"+delay+"'";
1394
1395
                    delay = QString("scxml.cssTime(%1)").arg(delay);
a6553f6 by Noam Rosenthal at 2009-06-07 1396
1397
                } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) {
1398
                    idLocation = r.attributes().value("idlocation").toString();
1399
                        if (idLocation.isEmpty())
1400
                            idLocation = r.attributes().value("invokeid").toString();
7a41ce5 by Noam Rosenthal at 2009-06-08 1401
                        QObject::connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("_data.invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec()));
a6553f6 by Noam Rosenthal at 2009-06-07 1402
1403
                    QString type = r.attributes().value("type").toString();
1404
                    if (type.isEmpty())
65e5705 by Noam Rosenthal at 2009-06-15 1405
                        type = "scxml";
a6553f6 by Noam Rosenthal at 2009-06-07 1406
                    curExecContext.type = ScExecContext::StateEntry;
1407
                    curExecContext.state = curState;
65e5705 by Noam Rosenthal at 2009-06-15 1408
                    paramNames.clear ();
1409
                    paramVals.clear ();
1410
                    content = "{}";
1411
                    target = r.attributes().value("src").toString();
1412
                    if (target == "")
1413
                        target = "\"\"";
1414
                    targetType = r.attributes().value("type").toString();
a6553f6 by Noam Rosenthal at 2009-06-07 1415
                } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) {
1416
                    if (curHistoryState) {
1417
                        ScHistoryInfo inf;
1418
                        inf.hstate = curHistoryState;
1419
                        inf.defaultStateID = r.attributes().value("target").toString();
1420
                        historyInfo.append(inf);
1421
                    } else {
1422
                        ScTransitionInfo inf;
1423
                        inf.targets = r.attributes().value("target").toString().split(' ');
1424
                        curExecContext.type = ScExecContext::Transition;
1425
                        curExecContext.script = "";
1426
                        curTransition = new QScxmlTransition(curState,stateMachine);
1427
                        curTransition->setConditionExpression(r.attributes().value("cond").toString());
9be5070 by Marc Schmitzer at 2012-01-27 1428
                        curTransition->setEventPrefixes(r.attributes().value("event").toString().split(' ', QString::SkipEmptyParts));
7a19605 by No'am Rosenthal at 2009-11-26 1429
                        foreach(QString pfx, curTransition->eventPrefixes()) {
1430
                            if (pfx != "*")
1431
                                stateMachine->pvt->knownEvents.insert(pfx);
1432
                        }
a6553f6 by Noam Rosenthal at 2009-06-07 1433
                        curExecContext.trans = curTransition;
1434
                        inf.transition = curTransition;
1435
                        transitions.append(inf);
7a19605 by No'am Rosenthal at 2009-11-26 1436
                        foreach (QString prefix, curTransition->eventPrefixes()) {
1437
                            if (prefix.startsWith("q-signal:")) {
1438
                                signalEvents.insert(prefix);
1439
                            }
a6553f6 by Noam Rosenthal at 2009-06-07 1440
                        }
9a50c13 by No'am Rosenthal at 2009-11-27 1441
                        curTransition->setObjectName(QString ("%1 to %2 on %3 if %4").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefixes().join(" ")).arg(curTransition->conditionExpression()));
a6553f6 by Noam Rosenthal at 2009-06-07 1442
                    }
9a50c13 by No'am Rosenthal at 2009-11-27 1443
                } else if (r.name().toString().compare("data",Qt::CaseInsensitive) == 0) {
a6553f6 by Noam Rosenthal at 2009-06-07 1444
                    QScriptValue val = qScriptValueFromValue<QString>(stateMachine->scriptEngine(),"")  ;
1445
                    QString id = r.attributes().value("id").toString();
1446
                    if (r.attributes().value("src").length())
1447
                        val = evaluateFile(QFileInfo(filename).dir().absoluteFilePath(r.attributes().value("src").toString()));
1448
                    else {
1449
                        if (r.attributes().value("expr").length()) {
1450
                            val = stateMachine->scriptEngine()->evaluate(r.attributes().value("expr").toString());
1451
                        } else {
1452
                            QString t = r.readElementText();
1453
                            if (!t.isEmpty())
1454
                                val = stateMachine->scriptEngine()->evaluate(t);
1455
                        }
1456
                    }
7a19605 by No'am Rosenthal at 2009-11-26 1457
                    QScriptValue func = stateMachine->scriptEngine()->newFunction(QScxmlFunctions::dataAccess);
1458
                    func.setProperty("key",id);
1459
                    stateMachine->pvt->dataObj.setProperty(id,func,QScriptValue::PropertyGetter|QScriptValue::PropertySetter);
1460
                    stateMachine->pvt->dataObj.setProperty(id,val);
a6553f6 by Noam Rosenthal at 2009-06-07 1461
                } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) {
65e5705 by Noam Rosenthal at 2009-06-15 1462
                    paramNames << r.attributes().value("name").toString();
1463
                    paramVals << r.attributes().value("expr").toString();
a6553f6 by Noam Rosenthal at 2009-06-07 1464
                } else if (r.name().toString().compare("content",Qt::CaseInsensitive) == 0) {
65e5705 by Noam Rosenthal at 2009-06-15 1465
                    content = r.readElementText();
a6553f6 by Noam Rosenthal at 2009-06-07 1466
                }
1467
        } else if (r.isEndElement()) {
1468
             if (r.name().toString().compare("state",Qt::CaseInsensitive) == 0 || r.name().toString().compare("parallel",Qt::CaseInsensitive) == 0) {
1469
                 if (curState == topLevelState) {
1470
                     return;
1471
                 } else {
1472
                     curState = qobject_cast<QState*>(curState->parent());
1473
                     curExecContext.state = curState;
1474
                 }
1475
             } else if (r.name().toString().compare("history",Qt::CaseInsensitive) == 0) {
1476
                 curHistoryState = NULL;
1477
             } else if (r.name().toString().compare("final",Qt::CaseInsensitive) == 0) {
d0441f6 by Noam Rosenthal at 2009-06-08 1478
                 curExecContext.state = (curExecContext.state->parentState());
65e5705 by Noam Rosenthal at 2009-06-15 1479
             } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) {
1480
                curExecContext.script += "}\n";
1481
                 } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0 || r.name().toString().compare("raise",Qt::CaseInsensitive) == 0) {
a6553f6 by Noam Rosenthal at 2009-06-07 1482
                if (!idLocation.isEmpty())
1483
                    curExecContext.script += idLocation + " = ";
65e5705 by Noam Rosenthal at 2009-06-15 1484
                    QString pnames;
1485
                    bool first = true;
1486
                    foreach (QString n, paramNames) {
1487
                        if (!first)
1488
                            pnames +=",";
1489
                        pnames += QString("\"%1\"").arg(n);
1490
                        first = false;
1491
                    }
1492
                    QString innerScript = QString("scxml.postEvent(%1,%2,\"%3\",[%4],[%5],%6);")
1493
                                                        .arg(eventName).arg(target).arg(targetType)
1494
                                                        .arg(pnames).arg(paramVals.join(",")).arg(content);
1495
                    if (target == "'_internal'")
1496
                        curExecContext.script += innerScript;
1497
                    else
1498
                        curExecContext.script += QString("scxml.setTimeout(function() {%1}, %2);")
1499
                                .arg(innerScript).arg(delay);
a6553f6 by Noam Rosenthal at 2009-06-07 1500
               idLocation = "";
65e5705 by Noam Rosenthal at 2009-06-15 1501
            } else if (    
a6553f6 by Noam Rosenthal at 2009-06-07 1502
                    r.name().toString().compare("onentry",Qt::CaseInsensitive) == 0
1503
                    || r.name().toString().compare("onexit",Qt::CaseInsensitive) == 0
1504
                    || r.name().toString().compare("scxml",Qt::CaseInsensitive) == 0) {
1505
                curExecContext.state = curState;
1506
                curExecContext.type = r.name().toString().compare("onexit",Qt::CaseInsensitive)==0 ? ScExecContext::StateExit : ScExecContext::StateEntry;
1507
                curExecContext.applyScript();
1508
                curExecContext.type = ScExecContext::None;
7a19605 by No'am Rosenthal at 2009-11-26 1509
                curExecContext.script = "";
a6553f6 by Noam Rosenthal at 2009-06-07 1510
            } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) {
1511
                if (!curHistoryState) {
1512
                    curExecContext.trans = curTransition;
1513
                    curExecContext.type = ScExecContext::Transition;
1514
                    curExecContext.applyScript();
eb07b34 by Marc Schmitzer at 2011-11-29 1515
                    curExecContext.script = "";
a6553f6 by Noam Rosenthal at 2009-06-07 1516
                }
1517
                curExecContext.type = ScExecContext::None;
1518
            } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) {
65e5705 by Noam Rosenthal at 2009-06-15 1519
                    QString pnames;
1520
                    bool first = true;
1521
                    foreach (QString n, paramNames) {
1522
                        if (!first)
1523
                            pnames +=",";
1524
                        pnames += QString("\"%1\"").arg(n);
1525
                        first = false;
1526
                    }
1527
                curExecContext.script +=  QString("_data.invoke_%1 = scxml.invoke(\"%2\",%3,[%4],[%5],%6); _data.invoke_%1.id = \"%1\";").arg(curState->objectName()).arg(targetType).arg(target).arg(pnames).arg(paramVals.join(",")).arg(content);
a6553f6 by Noam Rosenthal at 2009-06-07 1528
                if (!idLocation.isEmpty()) {
7a41ce5 by Noam Rosenthal at 2009-06-08 1529
                    curExecContext.script +=  QString("%1 = _data.invoke_%2;").arg(idLocation).arg(curState->objectName());
a6553f6 by Noam Rosenthal at 2009-06-07 1530
                }
1531
                curExecContext.state = curState;
1532
                curExecContext.type = ScExecContext::StateEntry;
1533
                curExecContext.applyScript();
1534
                idLocation = "";
1535
                curExecContext.type = ScExecContext::None;
1536
            }
1537
        }
1538
    }
1539
    }
1540
}
1541
7a19605 by No'am Rosenthal at 2009-11-26 1542
QMap<QString,QVariant> QScxml::data() const
1543
{
1544
    QMap<QString,QVariant> d;
1545
    QScriptValueIterator it (scriptEngine()->evaluate("_data"));
1546
    while (it.hasNext()) {
1547
        it.next();
1548
        d[it.name()] = it.value().toVariant();
1549
    }
1550
    return d;
1551
}
a6553f6 by Noam Rosenthal at 2009-06-07 1552
1553
QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename)
1554
{
65e5705 by Noam Rosenthal at 2009-06-15 1555
    if (device->bytesAvailable() == 0) {
1556
        qWarning() << QString("File %1 invalid or not found").arg(filename);
1557
        return NULL;
1558
    }
a6553f6 by Noam Rosenthal at 2009-06-07 1559
    stateMachine = new QScxml(obj);
1560
    // traverse through the states
7092b92 by No'am Rosenthal at 2009-07-23 1561
    loadState(stateMachine,device,"",filename);
a6553f6 by Noam Rosenthal at 2009-06-07 1562
1563
    // resolve history default state
1564
    foreach (ScHistoryInfo h, historyInfo) {
1565
        h.hstate->setDefaultState(stateByID[h.defaultStateID]);
1566
    }
1567
    foreach (QString s, signalEvents) {
1568
        QString sig = s;
1569
        sig = sig.mid(sig.indexOf(':')+1);
65e5705 by Noam Rosenthal at 2009-06-15 1570
        int liop = sig.lastIndexOf('.');
1571
        QString obj = sig.left(liop);
1572
        sig = sig.mid(liop+1);
1573
        stateMachine->pvt->startScript += QString("scxml.connectSignalToEvent(%1,'%2',\"%3\");").arg(obj).arg(sig).arg(s);
a6553f6 by Noam Rosenthal at 2009-06-07 1574
    }
7a41ce5 by Noam Rosenthal at 2009-06-08 1575
    
1576
    foreach (QState* s, statesWithFinal) {
1577
        QObject::connect(s,SIGNAL(finished()),stateMachine,SLOT(handleStateFinished()));
1578
    }
a6553f6 by Noam Rosenthal at 2009-06-07 1579
1580
    // resolve transitions
1581
1582
    foreach (ScTransitionInfo t, transitions) {
1583
        QList<QAbstractState*> states;
1584
        if (!t.targets.isEmpty()) {
1585
            foreach (QString s, t.targets) {
1586
                if (!s.trimmed().isEmpty()) {
1587
                    QAbstractState* st = stateByID[s];
1588
                    if (st)
1589
                        states.append(st);
1590
                }
1591
            }
1592
            t.transition->setTargetStates(states);
1593
        }
1594
    }
1595
1596
    return stateMachine;
1597
}
1598
d0441f6 by Noam Rosenthal at 2009-06-08 1599
void QScxml::handleStateFinished()
1600
{
7a41ce5 by Noam Rosenthal at 2009-06-08 1601
    QState* state = qobject_cast<QState*>(sender());
1602
    if (state) {
1603
        postEvent(new QScxmlEvent("done.state." + state->objectName()));
1604
    }
d0441f6 by Noam Rosenthal at 2009-06-08 1605
}
1606
a6553f6 by Noam Rosenthal at 2009-06-07 1607
/*!
1608
    Loads a state machine from an scxml file located at \a filename, with parent object \a o.
1609
  */
1610
QScxml* QScxml::load (const QString & filename, QObject* o)
1611
{
1612
    QScxmlLoader l;
1613
    QFile f (filename);
1614
    f.open(QIODevice::ReadOnly);
1615
    return l.load(&f,o,filename);
1616
}
1617
1618
#include "qscxml.moc"
65e5705 by Noam Rosenthal at 2009-06-15 1619
#endif