From 263a055f9b9460e4da747c7e56372e963f72fe68 Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Fri, 20 Feb 2026 12:32:56 +0100 Subject: Display all 30 nodes across 3 pages with human-readable tooltips NodePage rewritten with Repeater/ItemDelegate showing 10 nodes per page (RW Scalars, RO Scalars, RW Arrays). DataType resolved via namespace0Id helpers, AccessLevel decoded from bitmask to readable flags. --- demo/NodePage.qml | 266 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 139 insertions(+), 127 deletions(-) (limited to 'demo') diff --git a/demo/NodePage.qml b/demo/NodePage.qml index 2de0a15..1468817 100644 --- a/demo/NodePage.qml +++ b/demo/NodePage.qml @@ -1,4 +1,4 @@ -// NodePage.qml — Demo page displaying a single OPC UA node. +// NodePage.qml — Displays 10 OPC UA nodes per page with tooltips. import QtQuick import QtQuick.Controls @@ -12,167 +12,179 @@ Page { required property int pageNumber required property var logFunction - OpcUaMonitoredNode { - id: demoNode - nodeId: nodePage.pageNumber === 1 ? "ns=1;s=double_rw_scalar" : "ns=1;s=string_rw_scalar" - monitored: nodePage.StackView.status === StackView.Active - onMonitoredChanged: nodePage.logFunction("Page " + nodePage.pageNumber + " node [" + nodeId + "] " + (monitored ? "MONITORED" : "UNMONITORED")) - onValueChanged: nodePage.logFunction("Page " + nodePage.pageNumber + " value = " + value) - } + readonly property var pages: [ + { + title: "Read-Write Scalars", + nodes: [ + "ns=1;s=bool_rw_scalar", + "ns=1;s=int16_rw_scalar", + "ns=1;s=uint16_rw_scalar", + "ns=1;s=int32_rw_scalar", + "ns=1;s=uint32_rw_scalar", + "ns=1;s=int64_rw_scalar", + "ns=1;s=uint64_rw_scalar", + "ns=1;s=float_rw_scalar", + "ns=1;s=double_rw_scalar", + "ns=1;s=string_rw_scalar" + ] + }, + { + title: "Read-Only Scalars", + nodes: [ + "ns=1;s=bool_ro_scalar", + "ns=1;s=int16_ro_scalar", + "ns=1;s=uint16_ro_scalar", + "ns=1;s=int32_ro_scalar", + "ns=1;s=uint32_ro_scalar", + "ns=1;s=int64_ro_scalar", + "ns=1;s=uint64_ro_scalar", + "ns=1;s=float_ro_scalar", + "ns=1;s=double_ro_scalar", + "ns=1;s=string_ro_scalar" + ] + }, + { + title: "Read-Write Arrays", + nodes: [ + "ns=1;s=bool_rw_array", + "ns=1;s=int16_rw_array", + "ns=1;s=uint16_rw_array", + "ns=1;s=int32_rw_array", + "ns=1;s=uint32_rw_array", + "ns=1;s=int64_rw_array", + "ns=1;s=uint64_rw_array", + "ns=1;s=float_rw_array", + "ns=1;s=double_rw_array", + "ns=1;s=string_rw_array" + ] + } + ] + + readonly property var currentPage: pages[pageNumber - 1] + + Component.onCompleted: nodePage.logFunction( + currentPage.title + " page loaded (" + currentPage.nodes.length + " nodes)") ColumnLayout { anchors.fill: parent anchors.margins: 20 - spacing: 8 + spacing: 4 + // Header RowLayout { Label { - text: "Node Page " + nodePage.pageNumber + text: currentPage.title font.bold: true font.pointSize: 14 } - Item { - Layout.fillWidth: true + Label { + text: "(" + nodePage.pageNumber + "/3)" + color: "gray" } + Item { Layout.fillWidth: true } Button { text: "Disconnect" onClicked: Bobink.disconnectFromServer() } } - // Value (prominent) - Rectangle { - Layout.fillWidth: true - height: 60 - color: "#f0f0f0" - radius: 4 - - ColumnLayout { - anchors.fill: parent - anchors.margins: 8 - spacing: 2 - - Label { - text: "Value" - font.pointSize: 10 - color: "gray" - } - Label { - text: demoNode.value !== undefined ? String(demoNode.value) : "—" - font.pointSize: 16 - font.bold: true - elide: Text.ElideRight - Layout.fillWidth: true - } - } - } - - // Node info - GridLayout { + // Column headers + RowLayout { Layout.fillWidth: true - columns: 2 - columnSpacing: 12 - rowSpacing: 4 - - Label { - text: "Node ID:" - color: "gray" - } - Label { - text: demoNode.nodeId - Layout.fillWidth: true - } - - Label { - text: "Display Name:" - color: "gray" - } - Label { - text: demoNode.info.displayName || "—" - Layout.fillWidth: true - } - + spacing: 0 Label { - text: "Data Type:" - color: "gray" + text: "Identifier" + font.bold: true + Layout.preferredWidth: 200 + leftPadding: 12 } Label { - text: demoNode.info.dataType || "—" + text: "Value" + font.bold: true Layout.fillWidth: true } + } - Label { - text: "Node Class:" - color: "gray" - } - Label { - text: demoNode.info.nodeClass || "—" - Layout.fillWidth: true - } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#ccc" + } - Label { - text: "Access Level:" - color: "gray" - } - Label { - text: demoNode.info.accessLevel - Layout.fillWidth: true - } + // Nodes + Repeater { + model: currentPage.nodes - Label { - text: "Status:" - color: "gray" - } - Label { - text: demoNode.info.status || "—" + ItemDelegate { + id: delegate + required property string modelData + required property int index Layout.fillWidth: true - } + padding: 4 + leftPadding: 12 + rightPadding: 12 + + OpcUaMonitoredNode { + id: node + nodeId: delegate.modelData + monitored: nodePage.StackView.status === StackView.Active + } - Label { - text: "Source Time:" - color: "gray" - } - Label { - text: demoNode.info.sourceTimestamp.toLocaleString() || "—" - Layout.fillWidth: true - } + background: Rectangle { + color: delegate.index % 2 === 0 ? "#f8f8f8" : "transparent" + radius: 2 + } - Label { - text: "Server Time:" - color: "gray" - } - Label { - text: demoNode.info.serverTimestamp.toLocaleString() || "—" - Layout.fillWidth: true - } + contentItem: RowLayout { + spacing: 12 + Label { + text: { + var idx = node.nodeId.indexOf(";s="); + return idx >= 0 ? node.nodeId.substring(idx + 3) : node.nodeId; + } + Layout.preferredWidth: 188 + elide: Text.ElideRight + } + Label { + text: node.value !== undefined && String(node.value) !== "" ? String(node.value) : "—" + Layout.fillWidth: true + elide: Text.ElideRight + } + } - Label { - text: "Monitored:" - color: "gray" - } - Label { - text: demoNode.monitored ? "Yes" : "No" - color: demoNode.monitored ? "green" : "gray" - Layout.fillWidth: true + ToolTip.visible: hovered + ToolTip.delay: 400 + ToolTip.text: + "Display Name: " + (node.info.displayName || "—") + + "\nDescription: " + (node.info.description || "—") + + "\nNode Class: " + (node.info.nodeClass || "—") + + "\nData Type: " + (node.info.dataType || "—") + + "\nAccess Level: " + node.info.accessLevel + + "\nStatus: " + (node.info.status || "—") + + "\nSource Time: " + (node.info.sourceTimestamp.toLocaleString() || "—") + + "\nServer Time: " + (node.info.serverTimestamp.toLocaleString() || "—") } } - Item { - Layout.fillHeight: true - } + Item { Layout.fillHeight: true } - Button { + // Navigation + RowLayout { Layout.fillWidth: true - text: nodePage.pageNumber === 1 ? "Go to Page 2" : "Back to Page 1" - onClicked: { - if (nodePage.pageNumber === 1) - nodePage.stackRef.push("NodePage.qml", { - stackRef: nodePage.stackRef, - pageNumber: 2, - logFunction: nodePage.logFunction - }); - else - nodePage.stackRef.pop(); + Button { + text: "← Previous" + visible: nodePage.pageNumber > 1 + onClicked: nodePage.stackRef.pop() + } + Item { Layout.fillWidth: true } + Button { + text: "Next →" + visible: nodePage.pageNumber < 3 + onClicked: nodePage.stackRef.push("NodePage.qml", { + stackRef: nodePage.stackRef, + pageNumber: nodePage.pageNumber + 1, + logFunction: nodePage.logFunction + }) } } } -- cgit v1.2.3