KDStateMachineEditor API Documentation 2.1
Loading...
Searching...
No Matches
propertyeditor.cpp
Go to the documentation of this file.
1/*
2 This file is part of the KDAB State Machine Editor Library.
3
4 SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Kevin Funk <kevin.funk@kdab.com>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LicenseRef-KDAB-KDStateMachineEditor
8
9 Licensees holding valid commercial KDAB State Machine Editor Library
10 licenses may use this file in accordance with the KDAB State Machine Editor
11 Library License Agreement provided with the Software.
12
13 Contact info@kdab.com if any conditions of this licensing are not clear to you.
14*/
15
16#include "propertyeditor.h"
17
18#include "commandcontroller.h"
23#include "state.h"
24#include "transition.h"
25#include "elementmodel.h"
26#include "elementutil.h"
27#include "ui_statepropertyeditor.h"
28#include "ui_transitionpropertyeditor.h"
29
30#include <QDebug>
31#include <QHash>
32#include <QItemSelectionModel>
33#include <QMetaProperty>
34
35using namespace KDSME;
36
37namespace {
38
39QStringList allStates(const State *state)
40{
41 QStringList ret;
42 if (!state)
43 return ret;
44
45 if (!state->label().isEmpty())
46 ret << state->label();
47 const auto childStates = state->childStates();
48 for (const State *st : childStates)
49 ret << allStates(st);
50 ret.removeDuplicates();
51 return ret;
52}
53
54QStringList childStates(const State *state)
55{
56 QStringList ret;
57 ret << QString();
58 if (!state)
59 return ret;
60
61 const auto childStates = state->childStates();
62 for (const State *st : childStates) {
63 if (!st->label().isEmpty()) {
64 ret << st->label();
65 }
66 }
67
68 ret.removeDuplicates();
69 ret.sort();
70 return ret;
71}
72
73}
74
75struct PropertyEditor::Private // NOLINT(clang-analyzer-cplusplus.NewDelete)
76{
77 Private(PropertyEditor *q);
78
79 template<typename T>
80 [[nodiscard]] T *current() const
81 {
82 return qobject_cast<T *>(m_currentElement);
83 }
84
85 void setCurrentElement(KDSME::Element *element);
86
87 // slots
88 void updateSimpleProperty() const;
89 void setInitalState(const QString &label) const;
90 void setDefaultState(const QString &label) const;
91 void setSourceState(const QString &label) const;
92 void setTargetState(const QString &label) const;
93 void childModeChanged() const;
94 void currentChanged(const QModelIndex &current, const QModelIndex &previous);
95 void modelAboutToBeReset();
96 void loadFromCurrentElement() const;
97
99 QItemSelectionModel *m_selectionModel;
100 CommandController *m_commandController;
101 KDSME::StateModel *m_stateModel;
102 QPointer<KDSME::Element> m_currentElement;
103 Ui::StatePropertyEditor *m_stateWidget;
104 Ui::TransitionPropertyEditor *m_transitionWidget;
105 int m_noWidgetIndex, m_stateWidgetIndex, m_transitionWidgetIndex;
106
107 QHash<QObject *, QString> m_widgetToPropertyMap;
108};
109
110PropertyEditor::Private::Private(PropertyEditor *q)
111 : q(q)
112 , m_selectionModel(nullptr)
113 , m_commandController(nullptr)
114 , m_stateModel(nullptr)
115 , m_stateWidget(nullptr)
116 , m_transitionWidget(nullptr)
117 , m_noWidgetIndex(-1)
118 , m_stateWidgetIndex(-1)
119 , m_transitionWidgetIndex(-1)
120{
121}
122
124 : QStackedWidget(parent)
125 , d(new Private(this))
126{
127 d->m_stateWidget = new Ui::StatePropertyEditor;
128 d->m_transitionWidget = new Ui::TransitionPropertyEditor;
129 d->m_noWidgetIndex = addWidget(new QWidget(this));
130
131 auto *w = new QWidget(this);
132 d->m_stateWidget->setupUi(w);
133 d->m_stateWidgetIndex = addWidget(w);
134 w = new QWidget(this);
135 d->m_transitionWidget->setupUi(w);
136 d->m_transitionWidgetIndex = addWidget(w);
137
138 d->m_widgetToPropertyMap.insert(d->m_stateWidget->labelLineEdit, QStringLiteral("label"));
139 d->m_widgetToPropertyMap.insert(d->m_stateWidget->onEntryEditor, QStringLiteral("onEntry"));
140 d->m_widgetToPropertyMap.insert(d->m_stateWidget->onExitEditor, QStringLiteral("onExit"));
141 d->m_widgetToPropertyMap.insert(d->m_stateWidget->childModeEdit, QStringLiteral("childMode"));
142 d->m_widgetToPropertyMap.insert(d->m_stateWidget->historyTypeEdit, QStringLiteral("historyType"));
143 d->m_widgetToPropertyMap.insert(d->m_transitionWidget->labelLineEdit, QStringLiteral("label"));
144 d->m_widgetToPropertyMap.insert(d->m_transitionWidget->guardEditor, QStringLiteral("guard"));
145 d->m_widgetToPropertyMap.insert(d->m_transitionWidget->signalEdit, QStringLiteral("signal"));
146 d->m_widgetToPropertyMap.insert(d->m_transitionWidget->timeoutEdit, QStringLiteral("timeout"));
147
148 connect(d->m_stateWidget->labelLineEdit, SIGNAL(editingFinished()), this, SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
149 connect(d->m_stateWidget->initialStateComboBox, &QComboBox::textActivated, this, [this](const QString &text) { d->setInitalState(text); });
150 connect(d->m_stateWidget->defaultStateComboBox, &QComboBox::textActivated, this, [this](const QString &text) { d->setDefaultState(text); });
151 connect(d->m_stateWidget->onEntryEditor, SIGNAL(editingFinished(QString)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
152 connect(d->m_stateWidget->onExitEditor, SIGNAL(editingFinished(QString)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
153 connect(d->m_stateWidget->childModeEdit, SIGNAL(currentIndexChanged(int)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
154 connect(d->m_stateWidget->childModeEdit, SIGNAL(currentIndexChanged(int)), SLOT(childModeChanged())); // clazy:exclude=old-style-connect
155 connect(d->m_stateWidget->historyTypeEdit, SIGNAL(currentIndexChanged(int)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
156
157 connect(d->m_transitionWidget->labelLineEdit, SIGNAL(editingFinished()), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
158 connect(d->m_transitionWidget->sourceStateComboBox, &QComboBox::textActivated, this, [this](const QString &text) { d->setSourceState(text); });
159 connect(d->m_transitionWidget->targetStateComboBox, &QComboBox::textActivated, this, [this](const QString &text) { d->setTargetState(text); });
160 connect(d->m_transitionWidget->guardEditor, SIGNAL(editingFinished(QString)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
161 connect(d->m_transitionWidget->signalEdit, SIGNAL(editingFinished()), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
162 connect(d->m_transitionWidget->timeoutEdit, SIGNAL(valueChanged(int)), SLOT(updateSimpleProperty())); // clazy:exclude=old-style-connect
163
164 setCurrentIndex(d->m_noWidgetIndex);
165}
166
168{
169 delete d->m_stateWidget;
170 delete d->m_transitionWidget;
171}
172
173void PropertyEditor::setSelectionModel(QItemSelectionModel *selectionModel)
174{
175 if (d->m_selectionModel) {
176 // clang-format off
177 disconnect(d->m_selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), // clazy:exclude=old-style-connect
178 this, SLOT(currentChanged(QModelIndex,QModelIndex)));
179 disconnect(d->m_selectionModel->model(), SIGNAL(modelAboutToBeReset()), // clazy:exclude=old-style-connect
180 this, SLOT(modelAboutToBeReset()));
181 // clang-format on
182 }
183
184 d->m_selectionModel = selectionModel;
185
186 if (d->m_selectionModel) {
187 // clang-format off
188 connect(d->m_selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), // clazy:exclude=old-style-connect
189 this, SLOT(currentChanged(QModelIndex,QModelIndex)));
190 connect(d->m_selectionModel->model(), SIGNAL(modelAboutToBeReset()), // clazy:exclude=old-style-connect
191 this, SLOT(modelAboutToBeReset()));
192 // clang-format on
193 }
194}
195
197{
198 d->m_commandController = cmdController;
199}
200
202{
203 d->m_stateModel = selectionModel;
204}
205
206void PropertyEditor::Private::currentChanged(const QModelIndex &current, const QModelIndex &previous)
207{
208 Q_UNUSED(previous);
209 auto *currentElement = current.data(StateModel::ElementRole).value<Element *>();
210 setCurrentElement(currentElement);
211}
212
213void PropertyEditor::Private::modelAboutToBeReset()
214{
215 setCurrentElement(nullptr);
216}
217
218void PropertyEditor::Private::setCurrentElement(KDSME::Element *element)
219{
220 if (m_currentElement == element) {
221 return;
222 }
223
224 if (m_currentElement) {
225 QObject::disconnect(m_currentElement, nullptr, q, SLOT(loadFromCurrentElement()));
226 }
227
228 m_currentElement = element;
229
230 if (m_currentElement) {
231 for (int i = 0; i < m_currentElement->metaObject()->propertyCount(); ++i) {
232 const QMetaProperty prop = m_currentElement->metaObject()->property(i);
233 if (!prop.hasNotifySignal())
234 continue;
235 QObject::connect(m_currentElement, QByteArray { "2" + prop.notifySignal().methodSignature() }.constData(), q, SLOT(loadFromCurrentElement())); // krazy:exclude=doublequote_chars
236 }
237 }
238 loadFromCurrentElement();
239}
240
241void PropertyEditor::Private::loadFromCurrentElement() const // NOLINT(readability-function-cognitive-complexity)
242{
243 auto *state = current<State>();
244 if (state && state->flags().testFlag(Element::ElementIsEditable)) {
245 m_stateWidget->labelLineEdit->setText(state->label());
246 m_stateWidget->initialStateComboBox->clear();
247 m_stateWidget->defaultStateComboBox->clear();
248
249 m_stateWidget->initialStateLabel->setVisible(state->isComposite());
250 m_stateWidget->initialStateComboBox->setVisible(state->isComposite());
251 m_stateWidget->defaultStateLabel->setVisible(state->type() == Element::HistoryStateType);
252 m_stateWidget->defaultStateComboBox->setVisible(state->type() == Element::HistoryStateType);
253 m_stateWidget->childModeLabel->setVisible(state->isComposite());
254 m_stateWidget->childModeEdit->setVisible(state->isComposite());
255
256 if (state->isComposite()) {
257 m_stateWidget->initialStateComboBox->addItems(childStates(state));
258 State *initialState = ElementUtil::findInitialState(state);
259 if (initialState)
260 m_stateWidget->initialStateComboBox->setCurrentText(initialState->label());
261 else
262 m_stateWidget->initialStateComboBox->setCurrentIndex(0);
263
264 m_stateWidget->childModeEdit->setCurrentIndex(state->childMode());
265 }
266
267 if (state->type() == Element::HistoryStateType) {
268 m_stateWidget->defaultStateComboBox->addItems(allStates(state->machine()));
269 State *defaultState = qobject_cast<HistoryState *>(state)->defaultState();
270 m_stateWidget->defaultStateComboBox->setCurrentText(defaultState ? defaultState->label() : QLatin1String(""));
271 }
272
273 m_stateWidget->onEntryEditor->setPlainText(state->onEntry());
274 m_stateWidget->onExitEditor->setPlainText(state->onExit());
275
276 auto *historyState = current<HistoryState>();
277 m_stateWidget->historyTypeLabel->setVisible(historyState);
278 m_stateWidget->historyTypeEdit->setVisible(historyState);
279 if (historyState)
280 m_stateWidget->historyTypeEdit->setCurrentIndex(historyState->historyType());
281
282 q->setCurrentIndex(m_stateWidgetIndex); // State page
283
284 } else if (auto *transition = current<Transition>()) {
285 m_transitionWidget->labelLineEdit->setText(transition->label());
286
287 m_transitionWidget->sourceStateComboBox->clear();
288 State *sourceState = transition->sourceState();
289 Q_ASSERT(sourceState);
290 if (sourceState) {
291 m_transitionWidget->sourceStateComboBox->addItems(allStates(sourceState->machine()));
292 m_transitionWidget->sourceStateComboBox->setCurrentText(sourceState->label());
293 } else {
294 m_transitionWidget->sourceStateComboBox->setCurrentText(QString());
295 }
296 m_transitionWidget->targetStateComboBox->clear();
297 State *targetState = transition->targetState();
298 if (sourceState) {
299 m_transitionWidget->targetStateComboBox->addItems(allStates(sourceState->machine()));
300 } else {
301 m_transitionWidget->targetStateComboBox->setCurrentText(QString());
302 }
303 if (targetState)
304 m_transitionWidget->targetStateComboBox->setCurrentText(targetState->label());
305 else
306 m_transitionWidget->targetStateComboBox->setCurrentText(QString());
307
308 m_transitionWidget->guardEditor->setPlainText(transition->guard());
309 q->setCurrentIndex(m_transitionWidgetIndex); // Transition page
310
311 auto *signalTransition = current<SignalTransition>();
312 m_transitionWidget->signalLabel->setVisible(signalTransition);
313 m_transitionWidget->signalEdit->setVisible(signalTransition);
314 if (signalTransition)
315 m_transitionWidget->signalEdit->setText(signalTransition->signal());
316
317 auto *timeoutTransition = current<TimeoutTransition>();
318 m_transitionWidget->timeoutLabel->setVisible(timeoutTransition);
319 m_transitionWidget->timeoutEdit->setVisible(timeoutTransition);
320 if (timeoutTransition)
321 m_transitionWidget->timeoutEdit->setValue(timeoutTransition->timeout());
322
323 } else {
324 q->setCurrentIndex(m_noWidgetIndex);
325 }
326}
327
328void PropertyEditor::Private::updateSimpleProperty() const
329{
330 QObject *object = q->sender();
331 if (!object || !m_currentElement || !object->metaObject()->userProperty().isValid())
332 return;
333
334 const QString propertyName = m_widgetToPropertyMap.value(object);
335 Q_ASSERT(!propertyName.isEmpty());
336
337 const QVariant currentValue = m_currentElement->property(propertyName.toUtf8().constData());
338
339 QVariant newValue;
340 auto *comboBox = qobject_cast<QComboBox *>(object);
341#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
342 if (comboBox && currentValue.typeId() != QMetaType::QString) { // the user property on QComboBox is currentString, not always what we want
343#else
344 if (comboBox && currentValue.type() != QVariant::String) { // the user property on QComboBox is currentString, not always what we want
345#endif
346 newValue = comboBox->currentIndex();
347 } else {
348 newValue = object->metaObject()->userProperty().read(object);
349 }
350 if (currentValue == newValue)
351 return;
352
353 Q_ASSERT(m_commandController);
354 auto *command = new ModifyPropertyCommand(m_currentElement, propertyName.toUtf8().constData(), newValue);
355 m_commandController->undoStack()->push(command);
356}
357
358void PropertyEditor::Private::setInitalState(const QString &label) const
359{
360 auto *state = current<State>();
361 Q_ASSERT(state);
362 if (state) {
363 State *currentInitialState = ElementUtil::findInitialState(state);
364 State *initialState = ElementUtil::findState(state, label);
365 if (currentInitialState != initialState) {
366 auto *command = new ModifyInitialStateCommand(state, initialState);
367 m_commandController->undoStack()->push(command);
368 }
369 }
370}
371
372void PropertyEditor::Private::setDefaultState(const QString &label) const
373{
374 auto *state = current<HistoryState>();
375 Q_ASSERT(state);
376 if (state) {
377 State *defaultState = ElementUtil::findState(state->machine(), label);
378 if (state->defaultState() != defaultState) {
379 auto *command = new ModifyDefaultStateCommand(state, defaultState);
380 m_commandController->undoStack()->push(command);
381 }
382 }
383}
384
385void PropertyEditor::Private::setSourceState(const QString &label) const
386{
387 auto *transition = current<Transition>();
388 if (transition) {
389 State *sourceState = ElementUtil::findState(transition->sourceState()->machine(), label);
390 if (transition->sourceState() != sourceState) {
391 auto *command = new ModifyTransitionCommand(transition, m_stateModel);
392 command->setSourceState(sourceState);
393 m_commandController->undoStack()->push(command);
394 }
395 }
396}
397
398void PropertyEditor::Private::setTargetState(const QString &label) const
399{
400 auto *transition = current<Transition>();
401 if (transition) {
402 State *targetState = ElementUtil::findState(transition->sourceState()->machine(), label);
403 if (transition->targetState() != targetState) {
404 auto *command = new ModifyTransitionCommand(transition, m_stateModel);
405 command->setTargetState(targetState);
406 m_commandController->undoStack()->push(command);
407 }
408 }
409}
410
411void PropertyEditor::Private::childModeChanged() const
412{
413 const bool parallelMode = m_stateWidget->childModeEdit->currentIndex() == State::ParallelStates;
414
415 m_stateWidget->initialStateLabel->setEnabled(!parallelMode);
416 m_stateWidget->initialStateComboBox->setEnabled(!parallelMode);
417}
418
419#include "moc_propertyeditor.cpp"
@ ElementIsEditable
Definition element.h:74
QString label
Definition element.h:40
Flags flags
Definition element.h:39
Modifies the default state of a KDSME::HistoryState.
Modifies the initial state of a KDSME::State.
Modifies any specific property of a QObject.
Command for modifying properties of a KDSME::Transition.
void setStateModel(StateModel *selectionModel)
void setCommandController(CommandController *cmdController)
PropertyEditor(QWidget *parent=nullptr)
void setSelectionModel(QItemSelectionModel *selectionModel)
@ ElementRole
return Element*
ChildMode childMode
Definition state.h:33
QList< State * > childStates() const
Definition state.cpp:93
@ ParallelStates
Definition state.h:41
bool isComposite
Definition state.h:34
QString onExit
Definition state.h:32
Q_INVOKABLE KDSME::StateMachine * machine() const
Definition state.cpp:195
QString onEntry
Definition state.h:31
Type type() const override
Definition state.cpp:73
KDSME_CORE_EXPORT State * findInitialState(const State *state)
KDSME_CORE_EXPORT State * findState(State *root, const QString &label)

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDStateMachineEditor
Create Qt State Machine metacode using a graphical user interface
https://github.com/KDAB/KDStateMachineEditor
Generated on Tue Jul 15 2025 15:21:47 for KDStateMachineEditor API Documentation by doxygen 1.9.8