Skip to content

Commit 5581dd5

Browse files
authored
Merge pull request #15 from ModbusScope/dev/UI_type_dependant
Dev/UI type dependant
2 parents 5e62817 + bbd92db commit 5581dd5

27 files changed

Lines changed: 1274 additions & 699 deletions

adapters/describe.json

Lines changed: 256 additions & 229 deletions
Large diffs are not rendered by default.

adapters/dummymodbusadapter

13.6 KB
Binary file not shown.

adapters/dummymodbusadapter.exe

12.3 KB
Binary file not shown.

adapters/json-rpc-spec.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ Each property in `schema` includes the following additional fields (standard JSO
120120
| `title` | Standard JSON Schema annotation. UI-friendly label for the field, suitable for use in form inputs and dialog labels |
121121
| `x-enumLabels` | Custom extension. Present on enum properties only. A string array, parallel to `enum`, giving a UI-friendly display name for each allowed value |
122122

123+
The connection schema uses JSON Schema Draft 7 `if`/`then`/`else` to express type-dependent fields. When `type` equals `"tcp"`, the fields in `then.properties` apply (TCP-specific). Otherwise, when `type` is not `"tcp"`, the fields in `else.properties` apply (e.g., serial-specific or other non-TCP types). A UI can use this to enable or disable the relevant fields based on the selected connection type.
124+
123125
---
124126

125127
### `adapter.configure`

adapters/modbusadapter

9.59 KB
Binary file not shown.

adapters/modbusadapter.exe

12.3 KB
Binary file not shown.

src/customwidgets/deviceconfigtab.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <QComboBox>
99
#include <QHBoxLayout>
10+
#include <QJsonArray>
1011
#include <QLabel>
1112
#include <QLineEdit>
1213
#include <QVBoxLayout>
@@ -77,7 +78,13 @@ DeviceConfigTab::DeviceConfigTab(SettingsModel* pSettingsModel,
7778
void DeviceConfigTab::onAdapterChanged(int index)
7879
{
7980
QString newAdapterId = _pAdapterCombo->itemData(index).toString();
80-
rebuildSchemaForm(newAdapterId, QJsonObject());
81+
QJsonObject defaultValues;
82+
const QJsonArray defaultDevices = _pSettingsModel->adapterData(newAdapterId)->defaults().value("devices").toArray();
83+
if (!defaultDevices.isEmpty())
84+
{
85+
defaultValues = defaultDevices.first().toObject();
86+
}
87+
rebuildSchemaForm(newAdapterId, defaultValues);
8188
}
8289

8390
void DeviceConfigTab::rebuildSchemaForm(const QString& adapterId, const QJsonObject& deviceValues)

src/customwidgets/schemaformwidget.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <QSpinBox>
1010
#include <climits>
1111
#include <limits>
12+
#include <utility>
1213

1314
SchemaFormWidget::SchemaFormWidget(QWidget* parent) : QWidget(parent), _pFormLayout(new QFormLayout(this))
1415
{
@@ -18,6 +19,11 @@ SchemaFormWidget::SchemaFormWidget(QWidget* parent) : QWidget(parent), _pFormLay
1819
void SchemaFormWidget::setSchema(const QJsonObject& schema, const QJsonObject& values)
1920
{
2021
_fields.clear();
22+
_conditionalTriggerKey.clear();
23+
_conditionalTriggerConst.clear();
24+
_thenKeys.clear();
25+
_elseKeys.clear();
26+
_currentTriggerValue.clear();
2127

2228
// removeRow() deletes both the label and field widget
2329
while (_pFormLayout->rowCount() > 0)
@@ -72,6 +78,51 @@ void SchemaFormWidget::setSchema(const QJsonObject& schema, const QJsonObject& v
7278
_fields.append({ key, widget });
7379
_pFormLayout->addRow(label + ":", widget);
7480
}
81+
82+
if (!parseConditional(schema))
83+
{
84+
return;
85+
}
86+
87+
const QJsonObject thenProps = schema.value("then").toObject().value("properties").toObject();
88+
for (const QString& key : std::as_const(_thenKeys))
89+
{
90+
QJsonObject propSchema = thenProps.value(key).toObject();
91+
QString label = propSchema.value("title").toString(key);
92+
QWidget* widget = createWidgetForProperty(propSchema, values.value(key));
93+
_fields.append({ key, widget });
94+
_pFormLayout->addRow(label + ":", widget);
95+
}
96+
97+
const QJsonObject elseProps = schema.value("else").toObject().value("properties").toObject();
98+
for (const QString& key : std::as_const(_elseKeys))
99+
{
100+
QJsonObject propSchema = elseProps.value(key).toObject();
101+
QString label = propSchema.value("title").toString(key);
102+
QWidget* widget = createWidgetForProperty(propSchema, values.value(key));
103+
_fields.append({ key, widget });
104+
_pFormLayout->addRow(label + ":", widget);
105+
}
106+
107+
QComboBox* triggerCombo = nullptr;
108+
for (const auto& [key, widget] : std::as_const(_fields))
109+
{
110+
if (key == _conditionalTriggerKey)
111+
{
112+
triggerCombo = qobject_cast<QComboBox*>(widget);
113+
break;
114+
}
115+
}
116+
117+
if (triggerCombo != nullptr)
118+
{
119+
applyConditional(triggerCombo->currentData().toString());
120+
connect(triggerCombo, &QComboBox::currentIndexChanged, this, &SchemaFormWidget::onTriggerChanged);
121+
}
122+
else
123+
{
124+
applyConditional(values.value(_conditionalTriggerKey).toVariant().toString());
125+
}
75126
}
76127

77128
QWidget* SchemaFormWidget::createWidgetForProperty(const QJsonObject& propSchema, const QJsonValue& value)
@@ -155,11 +206,110 @@ QWidget* SchemaFormWidget::createWidgetForProperty(const QJsonObject& propSchema
155206
}
156207
}
157208

209+
bool SchemaFormWidget::parseConditional(const QJsonObject& schema)
210+
{
211+
const QJsonObject ifObj = schema.value("if").toObject();
212+
const QJsonObject thenObj = schema.value("then").toObject();
213+
const QJsonObject elseObj = schema.value("else").toObject();
214+
215+
if (ifObj.isEmpty() || thenObj.isEmpty() || elseObj.isEmpty())
216+
{
217+
return false;
218+
}
219+
220+
const QJsonArray required = ifObj.value("required").toArray();
221+
if (required.size() != 1)
222+
{
223+
return false;
224+
}
225+
226+
const QString triggerKey = required.at(0).toString();
227+
if (triggerKey.isEmpty())
228+
{
229+
return false;
230+
}
231+
232+
const QJsonObject ifProperties = ifObj.value("properties").toObject();
233+
const QJsonObject constObj = ifProperties.value(triggerKey).toObject();
234+
if (!constObj.contains("const"))
235+
{
236+
return false;
237+
}
238+
239+
_conditionalTriggerKey = triggerKey;
240+
_conditionalTriggerConst = constObj.value("const").toVariant().toString();
241+
242+
const QJsonObject thenProps = thenObj.value("properties").toObject();
243+
for (auto it = thenProps.constBegin(); it != thenProps.constEnd(); ++it)
244+
{
245+
_thenKeys.append(it.key());
246+
}
247+
248+
const QJsonObject elseProps = elseObj.value("properties").toObject();
249+
for (auto it = elseProps.constBegin(); it != elseProps.constEnd(); ++it)
250+
{
251+
_elseKeys.append(it.key());
252+
}
253+
254+
return true;
255+
}
256+
257+
void SchemaFormWidget::applyConditional(const QString& triggerValue)
258+
{
259+
_currentTriggerValue = triggerValue;
260+
const bool conditionMet = (triggerValue == _conditionalTriggerConst);
261+
262+
for (const auto& [key, widget] : std::as_const(_fields))
263+
{
264+
const bool isThen = _thenKeys.contains(key);
265+
const bool isElse = _elseKeys.contains(key);
266+
267+
if (!isThen && !isElse)
268+
{
269+
continue;
270+
}
271+
272+
const bool visible = isThen ? conditionMet : !conditionMet;
273+
widget->setVisible(visible);
274+
275+
QWidget* label = _pFormLayout->labelForField(widget);
276+
if (label != nullptr)
277+
{
278+
label->setVisible(visible);
279+
}
280+
}
281+
}
282+
283+
void SchemaFormWidget::onTriggerChanged(int /*index*/)
284+
{
285+
auto* combo = qobject_cast<QComboBox*>(sender());
286+
if (combo == nullptr)
287+
{
288+
return;
289+
}
290+
291+
applyConditional(combo->currentData().toString());
292+
}
293+
158294
QJsonObject SchemaFormWidget::values() const
159295
{
296+
const bool conditionMet = !_conditionalTriggerKey.isEmpty() && (_currentTriggerValue == _conditionalTriggerConst);
297+
160298
QJsonObject result;
161299
for (const auto& [key, widget] : _fields)
162300
{
301+
const bool isThen = _thenKeys.contains(key);
302+
const bool isElse = _elseKeys.contains(key);
303+
304+
if (isThen && !conditionMet)
305+
{
306+
continue;
307+
}
308+
if (isElse && conditionMet)
309+
{
310+
continue;
311+
}
312+
163313
if (auto* spin = qobject_cast<QSpinBox*>(widget))
164314
{
165315
result[key] = spin->value();

src/customwidgets/schemaformwidget.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <QJsonObject>
55
#include <QJsonValue>
6+
#include <QStringList>
67
#include <QWidget>
78

89
class QFormLayout;
@@ -14,6 +15,11 @@ class QFormLayout;
1415
* number, boolean, and enum-constrained string/integer properties.
1516
* The label for each row is taken from the property's \c "title" field, falling
1617
* back to the property key name if no title is provided.
18+
*
19+
* Supports the JSON Schema Draft 7 \c if/then/else pattern with a single-property
20+
* \c const condition. When detected, the active branch's fields are shown and the
21+
* inactive branch's fields are hidden, and visibility updates live as the trigger
22+
* field changes.
1723
*/
1824
class SchemaFormWidget : public QWidget
1925
{
@@ -32,15 +38,51 @@ class SchemaFormWidget : public QWidget
3238

3339
/*!
3440
* \brief Return current form input as a JSON object.
35-
* \return A QJsonObject with one entry per schema property.
41+
*
42+
* Fields belonging to the inactive conditional branch are omitted.
43+
* \return A QJsonObject with one entry per visible schema property.
3644
*/
3745
QJsonObject values() const;
3846

47+
private slots:
48+
//! Called when the trigger combo selection changes; re-evaluates conditional visibility.
49+
void onTriggerChanged(int index);
50+
3951
private:
4052
QWidget* createWidgetForProperty(const QJsonObject& propSchema, const QJsonValue& value);
4153

54+
/*!
55+
* \brief Parse the top-level \c if/then/else block and populate conditional state.
56+
*
57+
* Only the narrow single-property \c const pattern is supported.
58+
* \param schema Full schema object passed to setSchema().
59+
* \return \c true if a supported pattern was found and state was populated.
60+
*/
61+
bool parseConditional(const QJsonObject& schema);
62+
63+
/*!
64+
* \brief Show or hide rows based on whether the trigger value matches the const.
65+
* \param triggerValue Current string value of the trigger field.
66+
*/
67+
void applyConditional(const QString& triggerValue);
68+
4269
QFormLayout* _pFormLayout;
4370
QList<QPair<QString, QWidget*>> _fields;
71+
72+
//! Key in \c "properties" whose value drives the if/then/else switch.
73+
QString _conditionalTriggerKey;
74+
75+
//! The \c "const" value that activates the \c "then" branch.
76+
QString _conditionalTriggerConst;
77+
78+
//! Keys belonging to the \c "then" branch (shown when condition is true).
79+
QStringList _thenKeys;
80+
81+
//! Keys belonging to the \c "else" branch (shown when condition is false).
82+
QStringList _elseKeys;
83+
84+
//! Current value of the trigger field; used to filter \c values() output.
85+
QString _currentTriggerValue;
4486
};
4587

4688
#endif // SCHEMAFORMWIDGET_H

0 commit comments

Comments
 (0)