// NodePage.qml — Displays 10 OPC UA nodes per page with tooltips. import QtQuick import QtQuick.Controls import QtQuick.Layouts import Bobink Page { id: nodePage required property StackView stackRef required property int pageNumber required property var logFunction 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: 4 // Header RowLayout { Label { text: currentPage.title font.bold: true font.pointSize: 14 } Label { text: "(" + nodePage.pageNumber + "/3)" color: "gray" } Item { Layout.fillWidth: true } Button { text: "Disconnect" onClicked: Bobink.disconnectFromServer() } } // Column headers RowLayout { Layout.fillWidth: true Layout.leftMargin: 12 Layout.rightMargin: 12 spacing: 12 Label { text: "Identifier" font.bold: true Layout.preferredWidth: 160 } Label { text: "Value" font.bold: true Layout.preferredWidth: 160 } Label { text: "Write" font.bold: true Layout.fillWidth: true } } Rectangle { id: separator Layout.fillWidth: true height: 1 color: "#ccc" } // Nodes Repeater { model: currentPage.nodes 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 onWriteCompleted: (success, message) => { var short_id = node.nodeId.substring(node.nodeId.indexOf(";s=") + 3); nodePage.logFunction(short_id + ": " + message); } } background: Rectangle { color: delegate.index % 2 === 0 ? "#f8f8f8" : "transparent" radius: 2 } contentItem: RowLayout { spacing: 12 // Column 1: Identifier Label { text: { var idx = node.nodeId.indexOf(";s="); return idx >= 0 ? node.nodeId.substring(idx + 3) : node.nodeId; } Layout.preferredWidth: 160 elide: Text.ElideRight } // Column 2: Live value (always visible) Label { text: node.value !== undefined && String(node.value) !== "" ? String(node.value) : "—" Layout.preferredWidth: 160 elide: Text.ElideRight } // Column 3: Edit area (writable) or READ-ONLY label TextField { id: editField visible: node.writable Layout.fillWidth: true placeholderText: "Enter value..." onAccepted: node.writeValue(text) } Button { id: writeButton visible: node.writable text: "Write" onClicked: node.writeValue(editField.text) } Label { visible: !node.writable text: "(READ-ONLY)" color: "gray" font.italic: true 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 } // Navigation RowLayout { Layout.fillWidth: true 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 }) } } } }