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 | |
23 | QScxmlStateMachineLoader::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 | */ |
34 | QT_PREPEND_NAMESPACE(QScxmlStateMachine) *QScxmlStateMachineLoader::stateMachine() const |
35 | { |
36 | return m_stateMachine; |
37 | } |
38 | |
39 | void QScxmlStateMachineLoader::setStateMachine(QScxmlStateMachine* stateMachine) |
40 | { |
41 | if (m_stateMachine.value() != stateMachine) { |
42 | delete m_stateMachine.value(); |
43 | m_stateMachine = stateMachine; |
44 | } |
45 | } |
46 | |
47 | QBindable<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 | */ |
58 | QUrl QScxmlStateMachineLoader::source() |
59 | { |
60 | return m_source; |
61 | } |
62 | |
63 | void 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 | |
81 | QBindable<QUrl> QScxmlStateMachineLoader::bindableSource() |
82 | { |
83 | return &m_source; |
84 | } |
85 | |
86 | QVariantMap QScxmlStateMachineLoader::initialValues() const |
87 | { |
88 | return m_initialValues; |
89 | } |
90 | |
91 | void 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 | |
103 | QBindable<QVariantMap> QScxmlStateMachineLoader::bindableInitialValues() |
104 | { |
105 | return &m_initialValues; |
106 | } |
107 | |
108 | QScxmlDataModel *QScxmlStateMachineLoader::dataModel() const |
109 | { |
110 | return m_dataModel; |
111 | } |
112 | |
113 | void 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 | |
129 | QBindable<QScxmlDataModel*> QScxmlStateMachineLoader::bindableDataModel() |
130 | { |
131 | return &m_dataModel; |
132 | } |
133 | |
134 | bool 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 |