Skip to content

Dev/UI type dependant#15

Merged
jgeudens merged 8 commits intomasterfrom
dev/UI_type_dependant
Mar 31, 2026
Merged

Dev/UI type dependant#15
jgeudens merged 8 commits intomasterfrom
dev/UI_type_dependant

Conversation

@jgeudens
Copy link
Copy Markdown
Member

@jgeudens jgeudens commented Mar 30, 2026

Summary by CodeRabbit

  • New Features

    • Connection schema is conditional by type: TCP shows IP/port, other transports show serial fields.
    • New per-adapter settings pages render properties (arrays as tabs or single forms) and persist edits.
    • Settings UI now builds adapter pages dynamically; the old static connections page removed.
  • Improvements

    • Forms dynamically show/hide fields and omit values from inactive branches.
    • Device and item tabs are pre-populated from adapter-provided defaults.
  • Tests

    • Added/updated tests for conditional fields, defaults handling and adapter settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

Walkthrough

Adds Draft‑7 JSON Schema conditional (if/then/else) support to adapter describe schema and the schema-driven UI; SchemaFormWidget parses/applies conditionals to show/hide branch fields and filter returned values; adapter connection schema was converted to conditionals; AdapterConnectionSettings removed and replaced by a generic AdapterSettings that builds pages per property.

Changes

Cohort / File(s) Summary
Schema and docs
adapters/describe.json, adapters/json-rpc-spec.md
Reworked connections item schema to use Draft‑7 if/then/else keyed on type === "tcp" (TCP branch: ip, port; else: serial fields baudrate, databits, parity, portName, stopbits). Documentation updated to explain conditional usage.
SchemaFormWidget conditional support
src/customwidgets/schemaformwidget.h, src/customwidgets/schemaformwidget.cpp
Added parsing for narrow if/then/else patterns, stored trigger key/const, collected then/else keys, wired trigger combo changes to onTriggerChanged, applied show/hide logic via applyConditional, and filtered values() to omit inactive‑branch fields.
Device defaults propagation
src/customwidgets/deviceconfigtab.cpp, src/dialogs/adapterdevicesettings.cpp
When creating device tabs/forms, derive initial per‑device values from the first entry of adapter defaults().devices (if present) and use these defaults for tab creation and naming.
AdapterSettings and SettingsDialog refactor
src/dialogs/adaptersettings.h, src/dialogs/adaptersettings.cpp, src/dialogs/settingsdialog.h, src/dialogs/settingsdialog.cpp
Introduced AdapterSettings widget that renders array/object schema properties with tab support and persists edits; SettingsDialog now dynamically constructs per‑adapter property pages (excluding devices) and calls acceptValues() on all pages when accepting.
Removed connection settings implementation
src/dialogs/adapterconnectionsettings.h, src/dialogs/adapterconnectionsettings.cpp
Deleted the dedicated AdapterConnectionSettings class and its implementation; functionality replaced by the generic AdapterSettings.
Tests — SchemaFormWidget
tests/customwidgets/tst_schemaformwidget.h, tests/customwidgets/tst_schemaformwidget.cpp
Added tests covering conditional branch visibility on load and after switching, asserting values() omits inactive branch fields, and verifying label visibility/hiding.
Tests — Device config & device settings
tests/customwidgets/tst_deviceconfigtab.h, tests/customwidgets/tst_deviceconfigtab.cpp, tests/dialogs/tst_adapterdevicesettings.h, tests/dialogs/tst_adapterdevicesettings.cpp
Added tests asserting adapter switch uses device defaults and that adding device tabs uses defaults; updated tests to safer JSON access patterns.
Tests — AdapterSettings
tests/dialogs/tst_adaptersettings.h, tests/dialogs/tst_adaptersettings.cpp, tests/dialogs/CMakeLists.txt
Added comprehensive tests for AdapterSettings renderability, array vs object handling, tab creation with defaults, and persistence; updated CMake to add tst_adaptersettings.
Removed connection tests
tests/dialogs/tst_adapterconnectionsettings.h, tests/dialogs/tst_adapterconnectionsettings.cpp
Deleted the old AdapterConnectionSettings test suite and its CMake registration.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SettingsDialog
  participant AdapterSettings
  participant SchemaFormWidget
  participant SettingsModel

  User->>SettingsDialog: open dialog
  SettingsDialog->>AdapterSettings: construct per property (schema, config)
  AdapterSettings->>SchemaFormWidget: setSchema(schema), setValues(config)
  SchemaFormWidget->>SchemaFormWidget: parseConditional(if/then/else)
  User->>SchemaFormWidget: change trigger field (e.g., type combo)
  SchemaFormWidget->>SchemaFormWidget: onTriggerChanged -> applyConditional (show/hide fields)
  User->>SettingsDialog: accept dialog
  SettingsDialog->>AdapterSettings: acceptValues()
  AdapterSettings->>SchemaFormWidget: values()
  SchemaFormWidget-->>AdapterSettings: filtered JSON (only visible fields)
  AdapterSettings->>SettingsModel: setAdapterCurrentConfig(adapterId, updatedConfig)
Loading

Possibly related PRs

  • Update device + remove connection #14 — touches connection handling and UI/schema for connection fields; likely overlapping edits to connection schema and form rendering.
  • UI names #13 — modifies adapter describe JSON and schema-driven widgets; closely related to conditional/schema parsing changes.
  • Configuration (UI) #6 — earlier changes around SchemaFormWidget and DeviceConfigTab that this PR extends/refactors.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Dev/UI type dependant' is vague and does not clearly convey the main changes in the changeset, which involve implementing conditional schema logic for transport-specific fields and refactoring adapter settings UI. Consider using a more descriptive title that clearly summarises the primary change, such as 'Implement conditional schema for transport-specific adapter fields' or 'Refactor adapter settings UI with conditional field rendering'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/UI_type_dependant

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
tests/dialogs/tst_adapterdevicesettings.cpp (1)

134-137: Remove the redundant null-check after QVERIFY.

Line 133 already fails-and-returns on null in Qt Test, so Lines 134-137 are dead-path noise.

🧹 Optional cleanup
-    if (tabs == nullptr)
-    {
-        return;
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/dialogs/tst_adapterdevicesettings.cpp` around lines 134 - 137, Remove
the redundant null-check block that follows the Qt Test assertion: after the
QVERIFY(tabs) call, delete the if (tabs == nullptr) { return; } block so
execution relies on the test assertion rather than a dead-path return; this
targets the local variable tabs in tst_adapterdevicesettings.cpp (the code
surrounding the QVERIFY(tabs) check).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@adapters/json-rpc-spec.md`:
- Line 123: Update the sentence in adapters/json-rpc-spec.md that reads
"Otherwise the fields in `else.properties` apply (serial-specific)." to insert a
comma after "Otherwise" so it becomes "Otherwise, the fields in
`else.properties` apply (serial-specific)." This is a small punctuation/clarity
fix in the paragraph describing the JSON Schema Draft 7 `if`/`then`/`else`
behavior for the `type`-dependent connection fields.

In `@src/customwidgets/schemaformwidget.cpp`:
- Around line 107-120: The active branch must be derived from the actual trigger
widget state instead of the incoming JSON: find the trigger widget in _fields
(the same loop that qobject_casts to QComboBox) and after connecting the combo's
currentIndexChanged to SchemaFormWidget::onTriggerChanged, read the combo's
current value (e.g. combo->currentText() or combo->currentData()) and set
_currentTriggerValue accordingly, then call applyConditional using that
widget-derived value rather than values.value(_conditionalTriggerKey); ensure
this update happens before leaving the constructor/initialization so the visible
branch and values() are in sync.

In `@src/dialogs/adaptersettings.cpp`:
- Around line 66-79: The add-tab lambda attached to
AddableTabWidget::addTabRequested should be extracted into a named helper (e.g.,
a private method like createAndAddItemTab or addItemTab) and a monotonic counter
field (e.g., _nextItemTabIndex) should be introduced on the class to generate
unique tab titles instead of using _pItemTabs->count() + 1; inside the new
helper create the SchemaFormWidget, read defaults via
_pSettingsModel->adapterData(_adapterId)->defaults().value(_propertyKey).toArray(),
set the schema with defaultValues, build the title from _propertyKey and the
monotonic counter, increment the counter, and call _pItemTabs->addNewTab(name,
form); finally replace the multi-statement lambda passed to connect(...) with a
simple call to this new helper.
- Around line 17-18: The code creates a QVBoxLayout with this as parent and then
redundantly calls setLayout(layout); remove the unnecessary call to
setLayout(layout) so the QVBoxLayout(this) installation is relied upon—i.e.,
delete the setLayout(layout) line where QVBoxLayout is constructed to avoid
duplicate layout assignment.

In `@src/dialogs/settingsdialog.cpp`:
- Around line 99-120: The dialog currently persists settings on navigation and
on done regardless of accept/reject: remove the side-effect by stopping calls to
acceptAllValues() from settingsStackSwitch(int newRow) (leave it to only change
_pUi->settingsStack->setCurrentIndex(newRow)) and change
SettingsDialog::done(int r) to only call acceptAllValues() when r ==
QDialog::Accepted (i.e. gate persistence on explicit accept); keep references to
the functions acceptAllValues, done, and settingsStackSwitch to locate and
update the calls.

In `@tests/customwidgets/tst_schemaformwidget.cpp`:
- Around line 402-420: The test TestSchemaFormWidget::labelHiddenWithWidget uses
QLabel::isVisible() which is false for all children because the parent
SchemaFormWidget w is not shown; change the assertion to check the explicit
hidden state using QLabel::isHidden() instead (i.e., when iterating labels
inside labelHiddenWithWidget, replace the QVERIFY(!lbl->isVisible()) check with
QVERIFY(lbl->isHidden()) so the conditional hide logic is properly verified).

---

Nitpick comments:
In `@tests/dialogs/tst_adapterdevicesettings.cpp`:
- Around line 134-137: Remove the redundant null-check block that follows the Qt
Test assertion: after the QVERIFY(tabs) call, delete the if (tabs == nullptr) {
return; } block so execution relies on the test assertion rather than a
dead-path return; this targets the local variable tabs in
tst_adapterdevicesettings.cpp (the code surrounding the QVERIFY(tabs) check).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a9174d3-1a15-4a46-86a0-939b276e763b

📥 Commits

Reviewing files that changed from the base of the PR and between 5e62817 and ba9ec67.

⛔ Files ignored due to path filters (3)
  • adapters/dummymodbusadapter.exe is excluded by !**/*.exe
  • adapters/modbusadapter.exe is excluded by !**/*.exe
  • adapters/schemadump.exe is excluded by !**/*.exe
📒 Files selected for processing (25)
  • adapters/describe.json
  • adapters/dummymodbusadapter
  • adapters/json-rpc-spec.md
  • adapters/modbusadapter
  • src/customwidgets/deviceconfigtab.cpp
  • src/customwidgets/schemaformwidget.cpp
  • src/customwidgets/schemaformwidget.h
  • src/dialogs/adapterconnectionsettings.cpp
  • src/dialogs/adapterconnectionsettings.h
  • src/dialogs/adapterdevicesettings.cpp
  • src/dialogs/adaptersettings.cpp
  • src/dialogs/adaptersettings.h
  • src/dialogs/settingsdialog.cpp
  • src/dialogs/settingsdialog.h
  • tests/customwidgets/tst_deviceconfigtab.cpp
  • tests/customwidgets/tst_deviceconfigtab.h
  • tests/customwidgets/tst_schemaformwidget.cpp
  • tests/customwidgets/tst_schemaformwidget.h
  • tests/dialogs/CMakeLists.txt
  • tests/dialogs/tst_adapterconnectionsettings.cpp
  • tests/dialogs/tst_adapterconnectionsettings.h
  • tests/dialogs/tst_adapterdevicesettings.cpp
  • tests/dialogs/tst_adapterdevicesettings.h
  • tests/dialogs/tst_adaptersettings.cpp
  • tests/dialogs/tst_adaptersettings.h
💤 Files with no reviewable changes (4)
  • tests/dialogs/tst_adapterconnectionsettings.h
  • src/dialogs/adapterconnectionsettings.cpp
  • src/dialogs/adapterconnectionsettings.h
  • tests/dialogs/tst_adapterconnectionsettings.cpp

Comment thread adapters/json-rpc-spec.md Outdated
Comment thread src/customwidgets/schemaformwidget.cpp Outdated
Comment thread src/dialogs/adaptersettings.cpp Outdated
Comment thread src/dialogs/adaptersettings.cpp Outdated
Comment thread src/dialogs/settingsdialog.cpp Outdated
Comment thread src/dialogs/settingsdialog.cpp
Comment thread tests/customwidgets/tst_schemaformwidget.cpp
- adapters/json-rpc-spec.md: add comma after "Otherwise" (grammar)
- schemaformwidget.cpp: derive initial conditional branch from the
  trigger combo's current value instead of the incoming JSON values,
  preventing visible/values() mismatch on first render
- adaptersettings.cpp/h: remove redundant setLayout() call; extract
  add-tab lambda into named addItemTab() method with monotonic
  _nextItemTabIndex counter to avoid duplicate tab titles after a tab
  is closed
- settingsdialog.cpp: iterate all adapters (not just the first) when
  building dynamic settings pages; only persist settings when dialog
  is accepted (gate acceptAllValues on r == QDialog::Accepted) and
  remove side-effectful acceptAllValues() call from settingsStackSwitch
- tst_schemaformwidget.cpp: use isHidden() instead of isVisible() so
  the conditional-hide assertion works even when the parent is not shown

https://claude.ai/code/session_013ytSJa46akaAHkox9rjeRa
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/dialogs/adaptersettings.cpp (1)

57-57: Consider defensive check for empty _propertyKey.

Accessing _propertyKey[0] on an empty QString returns a null QChar, which would produce an unexpected tab name. While the caller (SettingsDialog) iterates schema properties ensuring non-empty keys, a defensive check would make this more robust.

🛡️ Proposed defensive check
+QString AdapterSettings::formatTabName(int index) const
+{
+    if (_propertyKey.isEmpty())
+    {
+        return QString("Item %1").arg(index);
+    }
+    return QString("%1 %2").arg(_propertyKey[0].toUpper() + _propertyKey.mid(1)).arg(index);
+}

Then use formatTabName(i + 1) at line 57 and formatTabName(_nextItemTabIndex) at lines 91-92.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dialogs/adaptersettings.cpp` at line 57, The tab-name construction uses
_propertyKey[0] without guarding for an empty QString; add a defensive helper
(e.g., formatTabName) that returns a safe display name when _propertyKey is
empty and otherwise builds "%1 %2" using the capitalized first character plus
mid(1) and the index. Replace the direct expression in names.append(...) (and
the usages at the sites that use _nextItemTabIndex) with a call to
formatTabName(i + 1) (and formatTabName(_nextItemTabIndex)) so _propertyKey is
never indexed when empty and tab names remain predictable.
tests/customwidgets/tst_schemaformwidget.cpp (1)

346-350: Redundant null check after QVERIFY.

QVERIFY(typeCombo != nullptr) at line 346 already fails the test if the pointer is null, making the subsequent if (typeCombo == nullptr) { return; } block unreachable. The same pattern appears at lines 376-380.

♻️ Proposed simplification
     QVERIFY(typeCombo != nullptr);
-    if (typeCombo == nullptr)
-    {
-        return;
-    }
     typeCombo->setCurrentIndex(typeCombo->findData(QStringLiteral("serial")));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/customwidgets/tst_schemaformwidget.cpp` around lines 346 - 350, The
test contains redundant null checks after assertions: remove the unreachable if
(typeCombo == nullptr) { return; } blocks that follow QVERIFY(typeCombo !=
nullptr) (and the same pattern around the later occurrence at lines referencing
typeCombo/assertions), leaving only the QVERIFY checks; this means locate the
QVERIFY(typeCombo != nullptr) assertions and delete the subsequent defensive
if-return blocks so the test relies on the assertion failure to stop execution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/dialogs/adaptersettings.cpp`:
- Line 57: The tab-name construction uses _propertyKey[0] without guarding for
an empty QString; add a defensive helper (e.g., formatTabName) that returns a
safe display name when _propertyKey is empty and otherwise builds "%1 %2" using
the capitalized first character plus mid(1) and the index. Replace the direct
expression in names.append(...) (and the usages at the sites that use
_nextItemTabIndex) with a call to formatTabName(i + 1) (and
formatTabName(_nextItemTabIndex)) so _propertyKey is never indexed when empty
and tab names remain predictable.

In `@tests/customwidgets/tst_schemaformwidget.cpp`:
- Around line 346-350: The test contains redundant null checks after assertions:
remove the unreachable if (typeCombo == nullptr) { return; } blocks that follow
QVERIFY(typeCombo != nullptr) (and the same pattern around the later occurrence
at lines referencing typeCombo/assertions), leaving only the QVERIFY checks;
this means locate the QVERIFY(typeCombo != nullptr) assertions and delete the
subsequent defensive if-return blocks so the test relies on the assertion
failure to stop execution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 36c596bb-e7ec-4509-bf58-aef8fb40fd51

📥 Commits

Reviewing files that changed from the base of the PR and between ba9ec67 and c57dd21.

📒 Files selected for processing (6)
  • adapters/json-rpc-spec.md
  • src/customwidgets/schemaformwidget.cpp
  • src/dialogs/adaptersettings.cpp
  • src/dialogs/adaptersettings.h
  • src/dialogs/settingsdialog.cpp
  • tests/customwidgets/tst_schemaformwidget.cpp
✅ Files skipped from review due to trivial changes (1)
  • adapters/json-rpc-spec.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/dialogs/settingsdialog.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/customwidgets/tst_schemaformwidget.cpp (1)

372-376: Remove redundant null-guard after QVERIFY.

After QVERIFY(typeCombo != nullptr);, the if (typeCombo == nullptr) { return; } branch is unnecessary and can be dropped for cleaner test flow.

Suggested simplification
     QVERIFY(typeCombo != nullptr);
-    if (typeCombo == nullptr)
-    {
-        return;
-    }
     typeCombo->setCurrentIndex(typeCombo->findData(QStringLiteral("tcp")));

As per coding guidelines, **/*.cpp: "Use early return only for error handling; avoid deep nesting".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/customwidgets/tst_schemaformwidget.cpp` around lines 372 - 376, The
if-check guarding typeCombo is redundant because QVERIFY(typeCombo != nullptr)
already aborts the test on null; remove the block "if (typeCombo == nullptr) {
return; }" from tst_schemaformwidget.cpp so the test flow relies on the QVERIFY
assertion and avoids unnecessary early return after the QCOMPARE/verification of
typeCombo.
src/dialogs/adaptersettings.cpp (1)

80-87: Avoid non-error early return in formatTabName().

This method uses an early return for normal control flow; please refactor to a single return path to match the .cpp guideline.

As per coding guidelines, "**/*.cpp: Use early return only for error handling; avoid deep nesting".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dialogs/adaptersettings.cpp` around lines 80 - 87,
AdapterSettings::formatTabName currently returns early when
_propertyKey.isEmpty(); refactor to a single return by computing the result in a
local QString (e.g., result), set result to "Item %1".arg(index) when
_propertyKey.isEmpty(), otherwise set result to QString("%1
%2").arg(_propertyKey[0].toUpper() + _propertyKey.mid(1)).arg(index), and return
result at the end; keep the same logic and use the existing symbols
(_propertyKey, formatTabName) without adding branching returns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/dialogs/adaptersettings.cpp`:
- Around line 29-31: The array check currently treats any non-empty "items" as
renderable but the code assumes object items; update the checks in
isRenderableProperty (and the same checks at the other two occurrences) to
verify the items schema is an object schema before returning true — e.g. get
propSchema.value("items").toObject() and require either
itemsObj.value("type").toString() == "object" or itemsObj.contains("properties")
(or both) instead of just !itemsObj.isEmpty(), so primitive item schemas are
excluded.
- Around line 11-127: Add brief Doxygen comment blocks for each public function
in this file to satisfy the repo rule: place a /** `@brief` ... */ (optionally
with `@param` and `@return` where appropriate) immediately above the definitions of
AdapterSettings::AdapterSettings, AdapterSettings::isRenderableProperty,
AdapterSettings::buildSection, AdapterSettings::formatTabName,
AdapterSettings::addItemTab, and AdapterSettings::acceptValues describing their
purpose and parameters/return values concisely; ensure comments use Doxygen
syntax and are short one-line briefs to match existing style.

---

Nitpick comments:
In `@src/dialogs/adaptersettings.cpp`:
- Around line 80-87: AdapterSettings::formatTabName currently returns early when
_propertyKey.isEmpty(); refactor to a single return by computing the result in a
local QString (e.g., result), set result to "Item %1".arg(index) when
_propertyKey.isEmpty(), otherwise set result to QString("%1
%2").arg(_propertyKey[0].toUpper() + _propertyKey.mid(1)).arg(index), and return
result at the end; keep the same logic and use the existing symbols
(_propertyKey, formatTabName) without adding branching returns.

In `@tests/customwidgets/tst_schemaformwidget.cpp`:
- Around line 372-376: The if-check guarding typeCombo is redundant because
QVERIFY(typeCombo != nullptr) already aborts the test on null; remove the block
"if (typeCombo == nullptr) { return; }" from tst_schemaformwidget.cpp so the
test flow relies on the QVERIFY assertion and avoids unnecessary early return
after the QCOMPARE/verification of typeCombo.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 764b1e67-c1ad-4164-8559-9a2a669d7a31

📥 Commits

Reviewing files that changed from the base of the PR and between c57dd21 and bbd92db.

⛔ Files ignored due to path filters (2)
  • adapters/dummymodbusadapter.exe is excluded by !**/*.exe
  • adapters/modbusadapter.exe is excluded by !**/*.exe
📒 Files selected for processing (7)
  • adapters/describe.json
  • adapters/dummymodbusadapter
  • adapters/json-rpc-spec.md
  • adapters/modbusadapter
  • src/dialogs/adaptersettings.cpp
  • src/dialogs/adaptersettings.h
  • tests/customwidgets/tst_schemaformwidget.cpp
✅ Files skipped from review due to trivial changes (2)
  • adapters/json-rpc-spec.md
  • src/dialogs/adaptersettings.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • adapters/describe.json

Comment on lines +11 to +127
AdapterSettings::AdapterSettings(SettingsModel* pSettingsModel,
const QString& adapterId,
const QString& propertyKey,
QWidget* parent)
: QWidget(parent), _pSettingsModel(pSettingsModel), _adapterId(adapterId), _propertyKey(propertyKey)
{
auto* layout = new QVBoxLayout(this);

const AdapterData* pAdapter = pSettingsModel->adapterData(adapterId);
const QJsonObject propSchema = pAdapter->schema().value("properties").toObject().value(propertyKey).toObject();
const QJsonValue configValue = pAdapter->effectiveConfig().value(propertyKey);

buildSection(propSchema, configValue, layout);
}

bool AdapterSettings::isRenderableProperty(const QJsonObject& propSchema)
{
const QString type = propSchema.value("type").toString();
if (type == "array")
{
return !propSchema.value("items").toObject().isEmpty();
}
if (type == "object")
{
return !propSchema.value("properties").toObject().isEmpty();
}
return false;
}

void AdapterSettings::buildSection(const QJsonObject& propSchema, const QJsonValue& configValue, QVBoxLayout* layout)
{
const QString type = propSchema.value("type").toString();

if (type == "array")
{
_itemSchema = propSchema.value("items").toObject();
_pItemTabs = new AddableTabWidget(this);

const QJsonArray itemsArray = configValue.toArray();
QList<QWidget*> pages;
QStringList names;
for (int i = 0; i < itemsArray.size(); ++i)
{
auto* form = new SchemaFormWidget(_pItemTabs);
form->setSchema(_itemSchema, itemsArray.at(i).toObject());
pages.append(form);
names.append(formatTabName(i + 1));
}

if (!pages.isEmpty())
{
_pItemTabs->setTabs(pages, names);
}

_nextItemTabIndex = itemsArray.size() + 1;

connect(_pItemTabs, &AddableTabWidget::addTabRequested, this, &AdapterSettings::addItemTab);

layout->addWidget(_pItemTabs, 1);
}
else
{
_pForm = new SchemaFormWidget(this);
_pForm->setSchema(propSchema, configValue.toObject());
layout->addWidget(_pForm);
layout->addStretch();
}
}

QString AdapterSettings::formatTabName(int index) const
{
if (_propertyKey.isEmpty())
{
return QString("Item %1").arg(index);
}
return QString("%1 %2").arg(_propertyKey[0].toUpper() + _propertyKey.mid(1)).arg(index);
}

void AdapterSettings::addItemTab()
{
auto* form = new SchemaFormWidget(_pItemTabs);
QJsonObject defaultValues;
const QJsonArray defaultItems = _pSettingsModel->adapterData(_adapterId)->defaults().value(_propertyKey).toArray();
if (!defaultItems.isEmpty())
{
defaultValues = defaultItems.first().toObject();
}
form->setSchema(_itemSchema, defaultValues);
const QString name = formatTabName(_nextItemTabIndex);
_nextItemTabIndex++;
_pItemTabs->addNewTab(name, form);
}

void AdapterSettings::acceptValues()
{
QJsonObject config = _pSettingsModel->adapterData(_adapterId)->effectiveConfig();

if (_pItemTabs)
{
QJsonArray itemsArray;
for (int i = 0; i < _pItemTabs->count(); ++i)
{
auto* form = qobject_cast<SchemaFormWidget*>(_pItemTabs->tabContent(i));
if (form)
{
itemsArray.append(form->values());
}
}
config[_propertyKey] = itemsArray;
}
else if (_pForm)
{
config[_propertyKey] = _pForm->values();
}

_pSettingsModel->setAdapterCurrentConfig(_adapterId, config);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add Doxygen comments for public functions in this source file.

The public functions here are missing brief Doxygen documentation blocks, which breaks the repo’s source-level documentation rule.

As per coding guidelines, "Document all public functions with brief Doxygen comments in the source file".

🧰 Tools
🪛 Cppcheck (2.20.0)

[error] 28-28: There is an unknown macro here somewhere. Configuration is required. If slots is a macro then please configure it.

(unknownMacro)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dialogs/adaptersettings.cpp` around lines 11 - 127, Add brief Doxygen
comment blocks for each public function in this file to satisfy the repo rule:
place a /** `@brief` ... */ (optionally with `@param` and `@return` where appropriate)
immediately above the definitions of AdapterSettings::AdapterSettings,
AdapterSettings::isRenderableProperty, AdapterSettings::buildSection,
AdapterSettings::formatTabName, AdapterSettings::addItemTab, and
AdapterSettings::acceptValues describing their purpose and parameters/return
values concisely; ensure comments use Doxygen syntax and are short one-line
briefs to match existing style.

Comment on lines +29 to +31
if (type == "array")
{
return !propSchema.value("items").toObject().isEmpty();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Constrain renderable array items to object schemas to avoid silent value corruption.

isRenderableProperty() currently accepts any array with non-empty items, but the array flow assumes each item is an object (toObject() on read; form->values() object on write). For primitive array schemas, saving can silently replace values with {} entries.

Proposed fix
 bool AdapterSettings::isRenderableProperty(const QJsonObject& propSchema)
 {
-    const QString type = propSchema.value("type").toString();
-    if (type == "array")
-    {
-        return !propSchema.value("items").toObject().isEmpty();
-    }
-    if (type == "object")
-    {
-        return !propSchema.value("properties").toObject().isEmpty();
-    }
-    return false;
+    bool renderable = false;
+    const QString type = propSchema.value("type").toString();
+    if (type == "array")
+    {
+        const QJsonObject itemSchema = propSchema.value("items").toObject();
+        renderable = itemSchema.value("type").toString() == "object"
+                     && !itemSchema.value("properties").toObject().isEmpty();
+    }
+    else if (type == "object")
+    {
+        renderable = !propSchema.value("properties").toObject().isEmpty();
+    }
+    return renderable;
 }

Also applies to: 55-56, 116-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dialogs/adaptersettings.cpp` around lines 29 - 31, The array check
currently treats any non-empty "items" as renderable but the code assumes object
items; update the checks in isRenderableProperty (and the same checks at the
other two occurrences) to verify the items schema is an object schema before
returning true — e.g. get propSchema.value("items").toObject() and require
either itemsObj.value("type").toString() == "object" or
itemsObj.contains("properties") (or both) instead of just !itemsObj.isEmpty(),
so primitive item schemas are excluded.

@jgeudens jgeudens merged commit 5581dd5 into master Mar 31, 2026
8 checks passed
@jgeudens jgeudens deleted the dev/UI_type_dependant branch March 31, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants