1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "statemachineloader_p.h"
5
6#include <QtScxml/qscxmlstatemachine.h>
7#include <qqmlcontext.h>
8#include <qqmlengine.h>
9#include <qqmlinfo.h>
10#include <qqmlfile.h>
11#include <qbuffer.h>
12
13/*!
14 \qmltype StateMachineLoader
15//! \instantiates QScxmlStateMachineLoader
16 \inqmlmodule QtScxml
17
18 \brief Dynamically loads an SCXML document and instantiates the state machine.
19
20 \since QtScxml 5.7
21 */
22
23QScxmlStateMachineLoader::QScxmlStateMachineLoader(QObject *parent)
24 : QObject(parent)
25 , m_implicitDataModel(nullptr)
26{
27}
28
29/*!
30 \qmlproperty ScxmlStateMachine StateMachineLoader::stateMachine
31
32 The state machine instance.
33 */
34QT_PREPEND_NAMESPACE(QScxmlStateMachine) *QScxmlStateMachineLoader::stateMachine() const
35{
36 return m_stateMachine;
37}
38
39void QScxmlStateMachineLoader::setStateMachine(QScxmlStateMachine* stateMachine)
40{
41 if (m_stateMachine.value() != stateMachine) {
42 delete m_stateMachine.value();
43 m_stateMachine = stateMachine;
44 }
45}
46
47QBindable<QScxmlStateMachine*> QScxmlStateMachineLoader::bindableStateMachine()
48{
49 return &m_stateMachine;
50}
51
52/*!
53 \qmlproperty url StateMachineLoader::source
54
55 The URL of the SCXML document to load. Only synchronously accessible URLs
56 are supported.
57 */
58QUrl QScxmlStateMachineLoader::source()
59{
60 return m_source;
61}
62
63void QScxmlStateMachineLoader::setSource(const QUrl &source)
64{
65 if (!source.isValid())
66 return;
67
68 const QUrl oldSource = m_source;
69 setStateMachine(nullptr);
70 m_implicitDataModel = nullptr;
71
72 if (parse(source))
73 m_source = source;
74 else
75 m_source = QUrl();
76
77 if (oldSource != m_source)
78 m_source.notify();
79}
80
81QBindable<QUrl> QScxmlStateMachineLoader::bindableSource()
82{
83 return &m_source;
84}
85
86QVariantMap QScxmlStateMachineLoader::initialValues() const
87{
88 return m_initialValues;
89}
90
91void QScxmlStateMachineLoader::setInitialValues(const QVariantMap &initialValues)
92{
93 if (initialValues == m_initialValues.value()) {
94 m_initialValues.removeBindingUnlessInWrapper();
95 return;
96 }
97 m_initialValues = initialValues;
98 if (m_stateMachine.value())
99 m_stateMachine.value()->setInitialValues(initialValues);
100 m_initialValues.notify();
101}
102
103QBindable<QVariantMap> QScxmlStateMachineLoader::bindableInitialValues()
104{
105 return &m_initialValues;
106}
107
108QScxmlDataModel *QScxmlStateMachineLoader::dataModel() const
109{
110 return m_dataModel;
111}
112
113void QScxmlStateMachineLoader::setDataModel(QScxmlDataModel *dataModel)
114{
115 if (dataModel == m_dataModel.value()) {
116 m_dataModel.removeBindingUnlessInWrapper();
117 return;
118 }
119 m_dataModel = dataModel;
120 if (m_stateMachine.value()) {
121 if (dataModel)
122 m_stateMachine.value()->setDataModel(dataModel);
123 else
124 m_stateMachine.value()->setDataModel(m_implicitDataModel);
125 }
126 m_dataModel.notify();
127}
128
129QBindable<QScxmlDataModel*> QScxmlStateMachineLoader::bindableDataModel()
130{
131 return &m_dataModel;
132}
133
134bool QScxmlStateMachineLoader::parse(const QUrl &source)
135{
136 if (!QQmlFile::isSynchronous(url: source)) {
137 qmlWarning(me: this) << QStringLiteral("Cannot open '%1' for reading: only synchronous access is supported.")
138 .arg(a: source.url());
139 return false;
140 }
141 QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), source);
142 if (scxmlFile.isError()) {
143 // the synchronous case can only fail when the file is not found (or not readable).
144 qmlWarning(me: this) << QStringLiteral("Cannot open '%1' for reading.").arg(a: source.url());
145 return false;
146 }
147
148 QByteArray data(scxmlFile.dataByteArray());
149 QBuffer buf(&data);
150 if (!buf.open(openMode: QIODevice::ReadOnly)) {
151 qmlWarning(me: this) << QStringLiteral("Cannot open input buffer for reading");
152 return false;
153 }
154
155 QString fileName;
156 if (source.isLocalFile()) {
157 fileName = source.toLocalFile();
158 } else if (source.scheme() == QStringLiteral("qrc")) {
159 fileName = QStringLiteral(":") + source.path();
160 } else {
161 qmlWarning(me: this) << QStringLiteral("%1 is neither a local nor a resource URL.")
162 .arg(a: source.url())
163 << QStringLiteral("Invoking services by relative path will not work.");
164 }
165
166 auto stateMachine = QScxmlStateMachine::fromData(data: &buf, fileName);
167 stateMachine->setParent(this);
168 m_implicitDataModel = stateMachine->dataModel();
169
170 if (stateMachine->parseErrors().isEmpty()) {
171 if (m_dataModel.value())
172 stateMachine->setDataModel(m_dataModel.value());
173 stateMachine->setInitialValues(m_initialValues.value());
174 setStateMachine(stateMachine);
175 // as this is deferred any pending property updates to m_dataModel and m_initialValues
176 // should still occur before start().
177 QMetaObject::invokeMethod(obj: m_stateMachine.value(), member: "start", c: Qt::QueuedConnection);
178 return true;
179 } else {
180 qmlWarning(me: this) << QStringLiteral("Something went wrong while parsing '%1':")
181 .arg(a: source.url())
182 << Qt::endl;
183 const auto errors = stateMachine->parseErrors();
184 for (const QScxmlError &error : errors) {
185 qmlWarning(me: this) << error.toString();
186 }
187 return false;
188 }
189}
190

source code of qtscxml/src/scxmlqml/statemachineloader.cpp