1
/****************************************************************************
2
**
3
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of SCXML on Qt labs
8
**
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$
39
**
40
****************************************************************************/
41
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
*/
53
#ifndef QT_NO_STATEMACHINE
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>
68
#include <QHistoryState>
69
#include <QFinalState>
70
#include <QState>
71
#include <QMetaMethod>
72
#include <QScriptProgram>
73
74
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
185
class QScxmlPrivate
186
{
187
    public:
188
189
        void initScriptEngine(QScxml* thiz);
190
191
        QScriptValue dataObj;
192
193
194
        QScriptEngine* scriptEng;
195
        QList<QScxmlInvokerFactory*> invokerFactories;
196
        QUrl burl;
197
        QString sessionID;
198
        QString startScript;
199
200
        QSet<QString> knownEvents;
201
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:
210
        QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine)
211
        {
212
            QTimer::singleShot(delay,this,SLOT(exec()));
213
            if (scr.isString()) {
214
                script = engine->evaluate(QString("function(){%1}").arg(scr.toString()));
215
            } else
216
                script = scr;
217
        }
218
    protected Q_SLOTS:
219
        void exec()
220
        {
221
222
            if (script.isFunction())
223
                script.call();
224
            deleteLater();
225
        }
226
        
227
    private:
228
        QScriptValue script;
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
}
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
};
267
struct QScxmlFunctions
268
{
269
static QScriptValue connectSignalToEvent(QScriptContext* context, QScriptEngine*)
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
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
334
static QScriptValue postEvent(QScriptContext *context, QScriptEngine *)
335
{
336
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
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;
365
                    scxml->postEvent(ev,QStateMachine::HighPriority);
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
{
396
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
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);
418
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
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
}
459
460
static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine)
461
{
462
    QScxml* scxml = qobject_cast<QScxml*>(context->thisObject().toQObject());
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
479
480
481
};
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();
497
    dataObj = scriptEng->newObject();
498
    dataObj.setProperty("_values",scriptEng->newObject());
499
    glob.setProperty("_data",dataObj);
500
    glob.setProperty("_global",scriptEng->globalObject());
501
    glob.setProperty("scxml",scxmlObj);
502
}
503
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
  */
579
  
580
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
}
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
}
606
/*!
607
   \reimp
608
*/
609
void QScxmlTransition::onTransition(QEvent*)
610
{
611
}
612
/*!
613
  \internal
614
  */
615
bool QScxmlTransition::eventTest(QEvent *e)
616
{
617
    QScriptEngine* engine = scxml->scriptEngine();
618
    QString event;
619
620
    if (e) {
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()) {
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
                     ;
635
        }
636
        if (!found  )
637
            return false;
638
    }
639
640
641
    if (!conditionExpression().isEmpty()) {
642
        QScriptValue v = engine->evaluate(prog);
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()));
652
            qDebug() << engine->uncaughtException().toString() << prog.sourceCode();
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
667
    
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
    }
680
    
681
    
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
    {
696
        cancelled = true;
697
        if (childSm)
698
            childSm->stop();
699
700
    }
701
    
702
    private:
703
        bool cancelled;
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
};
774
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
787
void QScxml::init()
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()));
795
    pvt->initScriptEngine(this);
796
}
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
}
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()));
829
            eventObj.setProperty("target",QScriptValue(se->metaData.target.toString()));
830
            eventObj.setProperty("targettype",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.targetType));
831
            eventObj.setProperty("invokeid",qScriptValueFromValue<QString>(pvt->scriptEng,se->metaData.invokeID));
832
            eventObj.setProperty("origin",QScriptValue(se->metaData.origin.toString()));
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
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
893
/*! \internal */
894
895
896
void QScxml::endMicrostep(QEvent*)
897
{
898
    scriptEngine()->globalObject().setProperty("_event",QScriptValue());
899
900
    emit configurationChanged();
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
}
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
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
955
void QScxml::executeScript (const QScriptProgram & s)
956
{
957
//    qDebug() << "Executing\n--------------------------\n"<<s.sourceCode();
958
        pvt->scriptEng->evaluate (s);
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())
964
                                            << QVariant(s.sourceCode())
965
                                            << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber())
966
                                            << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace()));
967
            e->metaData.kind = QScxmlEvent::MetaData::Platform;
968
            qDebug() << pvt->scriptEng->uncaughtException().toString() << s.sourceCode();
969
            postEvent(e);
970
            pvt->scriptEng->clearExceptions();
971
        }
972
//    qDebug() <<"\n--------------------\n";
973
}
974
void QScxml::executeScript (const QString & s)
975
{
976
    executeScript(QScriptProgram(s,baseUrl().toLocalFile()));
977
}
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
}
1048
/*! 
1049
returns the id for this invoker 
1050
*/
1051
QString QScxmlInvoker::id () const
1052
{
1053
    return initEvent->metaData.invokeID;
1054
}
1055
void QScxmlInvoker::setID(const QString & id)
1056
{
1057
    initEvent->metaData.invokeID = id;
1058
}
1059
1060
QScxmlInvoker::~QScxmlInvoker()
1061
{
1062
    if (cancelled)
1063
        postParentEvent("CancelResponse");
1064
    else
1065
        postParentEvent(QString("done.invoke.%1").arg(initEvent->metaData.invokeID));
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
1081
QStringList QScxml::knownEventNames() const
1082
{
1083
    return pvt->knownEvents.toList();
1084
}
1085
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;
1118
    ScTransitionInfo() : transition(NULL) {}
1119
};
1120
1121
class QScxmlScriptExec : public QObject
1122
{
1123
    Q_OBJECT
1124
    QScriptProgram prog;
1125
    QScxml* scxml;
1126
    public:
1127
        QScxmlScriptExec(const QString & scr, QScxml* scx) :
1128
                prog(QScriptProgram(scr,scx->baseUrl().toLocalFile())),scxml(scx)
1129
        {
1130
        }
1131
    public Q_SLOTS:
1132
        void exec()
1133
        {
1134
            scxml->executeScript(prog);
1135
        }
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:
1166
                    QObject::connect(state,SIGNAL(entered()),exec,SLOT(exec()));
1167
                break;
1168
                case StateExit:
1169
                    QObject::connect(state,SIGNAL(exited()),exec,SLOT(exec()));
1170
                break;
1171
                case Transition:
1172
                    QObject::connect(trans,SIGNAL(triggered()),exec,SLOT(exec()));
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;
1192
     QSet<QState*> statesWithFinal;
1193
   void loadState (QState* state, QIODevice* dev, const QString & stateID,const QString & filename);
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
1206
void QScxmlLoader::loadState (
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 = "";
1219
    QString idLocation, target, targetType, eventName, delay, content;
1220
    QStringList paramNames, paramVals;
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();
1235
                        if (curState == stateMachine) {
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
1256
                            if (curState == stateMachine)
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();
1291
                        curHistoryState = new QHistoryState(r.attributes().value("type") == "shallow" ? QHistoryState::ShallowHistory : QHistoryState::DeepHistory,curState);
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();
1298
                        QFinalState* f = new QFinalState(curState);
1299
                        f->setObjectName(id);
1300
                        curExecContext.state = f;
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
                        }
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) {
1313
                        stateMachine->executeScript(QScriptProgram(txt,stateMachine->baseUrl().toLocalFile()));
1314
                    } else
1315
                        curExecContext.script += txt;
1316
                } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) {
1317
                    curExecContext.script +=
1318
                            QString("scxml.print('[' + %1 + '][' + %2 + ']' + %3);")
1319
                            .arg(r.attributes().value("label").toString())
1320
                            .arg(r.attributes().value("level").toString())
1321
                            .arg(r.attributes().value("expr").toString());
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()) {
1331
                        curExecContext.script += QString ("%1 = %2;").arg(locattr).arg(r.attributes().value("expr").toString());
1332
                    }
1333
                } else if (r.name().toString().compare("if",Qt::CaseInsensitive) == 0) {
1334
                    curExecContext.script += QString("if (%1) {").arg(r.attributes().value("cond").toString());
1335
                } else if (r.name().toString().compare("elseif",Qt::CaseInsensitive) == 0) {
1336
                    curExecContext.script += QString("} elseif (%1) {").arg(r.attributes().value("cond").toString());
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) {
1340
                    curExecContext.script += QString("scxml.clearTimeout (%1);").arg(r.attributes().value("id").toString());
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 ) {
1348
                    eventName = r.attributes().value("event").toString();
1349
                    stateMachine->pvt->knownEvents.insert(eventName);
1350
                    eventName = QString("\"%1\"").arg(eventName);
1351
                    target = "'_internal'";
1352
                    targetType = "scxml";
1353
                    content = "{}";
1354
                    paramNames.clear();
1355
                    paramVals.clear();
1356
                } else if (r.name().toString().compare("send",Qt::CaseInsensitive) == 0) {
1357
                    paramNames.clear ();
1358
                    paramVals.clear();
1359
                    content = "{}";
1360
1361
                    target = r.attributes().value("target").toString();
1362
                    if (target == "") {
1363
                        target = r.attributes().value("targetexpr").toString();
1364
                        if (target == "")
1365
                            target = "\"\"";
1366
                    } else
1367
                        target = "\""+target+"\"";
1368
                    targetType = r.attributes().value("type").toString();
1369
                    eventName = r.attributes().value("event").toString();
1370
                    if (eventName == "") {
1371
                        eventName = r.attributes().value("eventexpr").toString();
1372
                    } else {
1373
                        stateMachine->pvt->knownEvents.insert(eventName);
1374
                        eventName = "'"+eventName+"'";
1375
                    }
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
                    }
1383
                    idLocation = r.attributes().value("idlocation").toString();
1384
                    if (idLocation.isEmpty())
1385
                        idLocation = r.attributes().value("sendid").toString();
1386
                        
1387
                    delay = r.attributes().value("delay").toString();
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);
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();
1401
                        QObject::connect (curState, SIGNAL(exited()),new QScxmlScriptExec(QString("_data.invoke_%1.cancel();").arg(curState->objectName()),stateMachine),SLOT(exec()));
1402
1403
                    QString type = r.attributes().value("type").toString();
1404
                    if (type.isEmpty())
1405
                        type = "scxml";
1406
                    curExecContext.type = ScExecContext::StateEntry;
1407
                    curExecContext.state = curState;
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();
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());
1428
                        curTransition->setEventPrefixes(r.attributes().value("event").toString().split(' ', QString::SkipEmptyParts));
1429
                        foreach(QString pfx, curTransition->eventPrefixes()) {
1430
                            if (pfx != "*")
1431
                                stateMachine->pvt->knownEvents.insert(pfx);
1432
                        }
1433
                        curExecContext.trans = curTransition;
1434
                        inf.transition = curTransition;
1435
                        transitions.append(inf);
1436
                        foreach (QString prefix, curTransition->eventPrefixes()) {
1437
                            if (prefix.startsWith("q-signal:")) {
1438
                                signalEvents.insert(prefix);
1439
                            }
1440
                        }
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()));
1442
                    }
1443
                } else if (r.name().toString().compare("data",Qt::CaseInsensitive) == 0) {
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
                    }
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);
1461
                } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) {
1462
                    paramNames << r.attributes().value("name").toString();
1463
                    paramVals << r.attributes().value("expr").toString();
1464
                } else if (r.name().toString().compare("content",Qt::CaseInsensitive) == 0) {
1465
                    content = r.readElementText();
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) {
1478
                 curExecContext.state = (curExecContext.state->parentState());
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) {
1482
                if (!idLocation.isEmpty())
1483
                    curExecContext.script += idLocation + " = ";
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);
1500
               idLocation = "";
1501
            } else if (    
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;
1509
                curExecContext.script = "";
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();
1515
                    curExecContext.script = "";
1516
                }
1517
                curExecContext.type = ScExecContext::None;
1518
            } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) {
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);
1528
                if (!idLocation.isEmpty()) {
1529
                    curExecContext.script +=  QString("%1 = _data.invoke_%2;").arg(idLocation).arg(curState->objectName());
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
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
}
1552
1553
QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename)
1554
{
1555
    if (device->bytesAvailable() == 0) {
1556
        qWarning() << QString("File %1 invalid or not found").arg(filename);
1557
        return NULL;
1558
    }
1559
    stateMachine = new QScxml(obj);
1560
    // traverse through the states
1561
    loadState(stateMachine,device,"",filename);
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);
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);
1574
    }
1575
    
1576
    foreach (QState* s, statesWithFinal) {
1577
        QObject::connect(s,SIGNAL(finished()),stateMachine,SLOT(handleStateFinished()));
1578
    }
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
1599
void QScxml::handleStateFinished()
1600
{
1601
    QState* state = qobject_cast<QState*>(sender());
1602
    if (state) {
1603
        postEvent(new QScxmlEvent("done.state." + state->objectName()));
1604
    }
1605
}
1606
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"
1619
#endif