KDStateMachineEditor API Documentation 2.3
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 (comboBox && currentValue.typeId() != QMetaType::QString) { // the user property on QComboBox is currentString, not always what we want
342 newValue = comboBox->currentIndex();
343 } else {
344 newValue = object->metaObject()->userProperty().read(object);
345 }
346 if (currentValue == newValue)
347 return;
348
349 Q_ASSERT(m_commandController);
350 auto *command = new ModifyPropertyCommand(m_currentElement, propertyName.toUtf8().constData(), newValue);
351 m_commandController->undoStack()->push(command);
352}
353
354void PropertyEditor::Private::setInitalState(const QString &label) const
355{
356 auto *state = current<State>();
357 Q_ASSERT(state);
358 if (state) {
359 State *currentInitialState = ElementUtil::findInitialState(state);
360 State *initialState = ElementUtil::findState(state, label);
361 if (currentInitialState != initialState) {
362 auto *command = new ModifyInitialStateCommand(state, initialState);
363 m_commandController->undoStack()->push(command);
364 }
365 }
366}
367
368void PropertyEditor::Private::setDefaultState(const QString &label) const
369{
370 auto *state = current<HistoryState>();
371 Q_ASSERT(state);
372 if (state) {
373 State *defaultState = ElementUtil::findState(state->machine(), label);
374 if (state->defaultState() != defaultState) {
375 auto *command = new ModifyDefaultStateCommand(state, defaultState);
376 m_commandController->undoStack()->push(command);
377 }
378 }
379}
380
381void PropertyEditor::Private::setSourceState(const QString &label) const
382{
383 auto *transition = current<Transition>();
384 if (transition) {
385 State *sourceState = ElementUtil::findState(transition->sourceState()->machine(), label);
386 if (transition->sourceState() != sourceState) {
387 auto *command = new ModifyTransitionCommand(transition, m_stateModel);
388 command->setSourceState(sourceState);
389 m_commandController->undoStack()->push(command);
390 }
391 }
392}
393
394void PropertyEditor::Private::setTargetState(const QString &label) const
395{
396 auto *transition = current<Transition>();
397 if (transition) {
398 State *targetState = ElementUtil::findState(transition->sourceState()->machine(), label);
399 if (transition->targetState() != targetState) {
400 auto *command = new ModifyTransitionCommand(transition, m_stateModel);
401 command->setTargetState(targetState);
402 m_commandController->undoStack()->push(command);
403 }
404 }
405}
406
407void PropertyEditor::Private::childModeChanged() const
408{
409 const bool parallelMode = m_stateWidget->childModeEdit->currentIndex() == State::ParallelStates;
410
411 m_stateWidget->initialStateLabel->setEnabled(!parallelMode);
412 m_stateWidget->initialStateComboBox->setEnabled(!parallelMode);
413}
414
415#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 Mon Mar 9 2026 12:16:36 for KDStateMachineEditor API Documentation by doxygen 1.9.8