1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtScxml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "statemachineloader_p.h"
41
42#include <QtScxml/qscxmlstatemachine.h>
43#include <qqmlcontext.h>
44#include <qqmlengine.h>
45#include <qqmlinfo.h>
46#include <qqmlfile.h>
47#include <qbuffer.h>
48
49/*!
50 \qmltype StateMachineLoader
51 \instantiates QScxmlStateMachineLoader
52 \inqmlmodule QtScxml
53
54 \brief Dynamically loads an SCXML document and instantiates the state machine.
55
56 \since QtScxml 5.7
57 */
58
59QScxmlStateMachineLoader::QScxmlStateMachineLoader(QObject *parent)
60 : QObject(parent)
61 , m_dataModel(nullptr)
62 , m_implicitDataModel(nullptr)
63 , m_stateMachine(nullptr)
64{
65}
66
67/*!
68 \qmlproperty ScxmlStateMachine StateMachineLoader::stateMachine
69
70 The state machine instance.
71 */
72QT_PREPEND_NAMESPACE(QScxmlStateMachine) *QScxmlStateMachineLoader::stateMachine() const
73{
74 return m_stateMachine;
75}
76
77/*!
78 \qmlproperty url StateMachineLoader::source
79
80 The URL of the SCXML document to load. Only synchronously accessible URLs
81 are supported.
82 */
83QUrl QScxmlStateMachineLoader::source()
84{
85 return m_source;
86}
87
88void QScxmlStateMachineLoader::setSource(const QUrl &source)
89{
90 if (!source.isValid())
91 return;
92
93 QUrl oldSource = m_source;
94 if (m_stateMachine) {
95 delete m_stateMachine;
96 m_stateMachine = nullptr;
97 m_implicitDataModel = nullptr;
98 }
99
100 if (parse(source)) {
101 m_source = source;
102 emit sourceChanged();
103 } else {
104 m_source.clear();
105 if (!oldSource.isEmpty()) {
106 emit sourceChanged();
107 }
108 }
109}
110
111QVariantMap QScxmlStateMachineLoader::initialValues() const
112{
113 return m_initialValues;
114}
115
116void QScxmlStateMachineLoader::setInitialValues(const QVariantMap &initialValues)
117{
118 if (initialValues != m_initialValues) {
119 m_initialValues = initialValues;
120 if (m_stateMachine)
121 m_stateMachine->setInitialValues(initialValues);
122 emit initialValuesChanged();
123 }
124}
125
126QScxmlDataModel *QScxmlStateMachineLoader::dataModel() const
127{
128 return m_dataModel;
129}
130
131void QScxmlStateMachineLoader::setDataModel(QScxmlDataModel *dataModel)
132{
133 if (dataModel != m_dataModel) {
134 m_dataModel = dataModel;
135 if (m_stateMachine) {
136 if (dataModel)
137 m_stateMachine->setDataModel(dataModel);
138 else
139 m_stateMachine->setDataModel(m_implicitDataModel);
140 }
141 emit dataModelChanged();
142 }
143}
144
145bool QScxmlStateMachineLoader::parse(const QUrl &source)
146{
147 if (!QQmlFile::isSynchronous(url: source)) {
148 qmlWarning(me: this) << QStringLiteral("Cannot open '%1' for reading: only synchronous access is supported.")
149 .arg(a: source.url());
150 return false;
151 }
152 QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), source);
153 if (scxmlFile.isError()) {
154 // the synchronous case can only fail when the file is not found (or not readable).
155 qmlWarning(me: this) << QStringLiteral("Cannot open '%1' for reading.").arg(a: source.url());
156 return false;
157 }
158
159 QByteArray data(scxmlFile.dataByteArray());
160 QBuffer buf(&data);
161 if (!buf.open(openMode: QIODevice::ReadOnly)) {
162 qmlWarning(me: this) << QStringLiteral("Cannot open input buffer for reading");
163 return false;
164 }
165
166 QString fileName;
167 if (source.isLocalFile()) {
168 fileName = source.toLocalFile();
169 } else if (source.scheme() == QStringLiteral("qrc")) {
170 fileName = ":" + source.path();
171 } else {
172 qmlWarning(me: this) << QStringLiteral("%1 is neither a local nor a resource URL.")
173 .arg(a: source.url())
174 << QStringLiteral("Invoking services by relative path will not work.");
175 }
176
177 m_stateMachine = QScxmlStateMachine::fromData(data: &buf, fileName);
178 m_stateMachine->setParent(this);
179 m_implicitDataModel = m_stateMachine->dataModel();
180
181 if (m_stateMachine->parseErrors().isEmpty()) {
182 if (m_dataModel)
183 m_stateMachine->setDataModel(m_dataModel);
184 m_stateMachine->setInitialValues(m_initialValues);
185 emit stateMachineChanged();
186
187 // as this is deferred any pending property updates to m_dataModel and m_initialValues
188 // should still occur before start().
189 QMetaObject::invokeMethod(obj: m_stateMachine, member: "start", type: Qt::QueuedConnection);
190 return true;
191 } else {
192 qmlWarning(me: this) << QStringLiteral("Something went wrong while parsing '%1':")
193 .arg(a: source.url())
194 << Qt::endl;
195 const auto errors = m_stateMachine->parseErrors();
196 for (const QScxmlError &error : errors) {
197 qmlWarning(me: this) << error.toString();
198 }
199
200 emit stateMachineChanged();
201 return false;
202 }
203}
204

source code of qtscxml/src/imports/scxmlstatemachine/statemachineloader.cpp