feat: wire family
code display and status restore in StatusTab + HomePage
This commit is contained in:
@@ -323,6 +323,7 @@ Rectangle {
|
|||||||
label: "READ MEMORY"
|
label: "READ MEMORY"
|
||||||
iconSource: "../icons/read.svg"
|
iconSource: "../icons/read.svg"
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
enabled: root.waferDetected
|
||||||
onClicked: deviceController.readMemoryAsync()
|
onClicked: deviceController.readMemoryAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +332,7 @@ Rectangle {
|
|||||||
iconSource: "../icons/erase.svg"
|
iconSource: "../icons/erase.svg"
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
destructive: true
|
destructive: true
|
||||||
|
enabled: root.waferDetected
|
||||||
onClicked: deviceController.eraseMemory(
|
onClicked: deviceController.eraseMemory(
|
||||||
deviceController.selectedPort)
|
deviceController.selectedPort)
|
||||||
}
|
}
|
||||||
|
|||||||
+504
-362
@@ -4,37 +4,106 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
import ISC
|
import ISC
|
||||||
|
|
||||||
// ===== Status Tab =====
|
// ===== Status Tab — Bento Grid Dashboard =====
|
||||||
// Shows connection status, wafer info, and activity log.
|
// ROW 1: Connection Status (65%) | Cycles Completed (35%)
|
||||||
// Initially visible — displays connection status and activity log from start.
|
// ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%)
|
||||||
|
// ROW 3: Activity Log (100%, fills remaining)
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.panelPadding
|
anchors.margins: Theme.panelPadding
|
||||||
spacing: Theme.rightPaneGap
|
spacing: 8
|
||||||
|
|
||||||
property alias waferFamilyCode: waferInfoFamily.text
|
// ── Reusable Inline Components ──
|
||||||
property alias waferSerialNumber: waferSerial.text
|
component StatCard : Rectangle {
|
||||||
property alias waferSensorCount: waferSensors.text
|
property alias value: valueText.text
|
||||||
property alias waferRuntime: waferRuntime.text
|
property alias label: labelText.text
|
||||||
property alias waferCycles: waferCycles.text
|
property bool active: root.waferDetected
|
||||||
property bool waferDetected: false
|
property int valSize: active ? Theme.font3xl : Theme.font2xl
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Theme.cardBackground
|
||||||
|
border.color: Theme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
Text {
|
||||||
|
id: valueText
|
||||||
|
color: active ? Theme.headingColor : Theme.sideMutedText
|
||||||
|
font.pixelSize: valSize
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: labelText
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.letterSpacing: 1.2
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component GridCell : ColumnLayout {
|
||||||
|
property alias label: labelText.text
|
||||||
|
property alias value: valueText.text
|
||||||
|
property bool isRight: false
|
||||||
|
property bool active: root.waferDetected
|
||||||
|
|
||||||
|
spacing: 1
|
||||||
|
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: labelText
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: valueText
|
||||||
|
color: active ? Theme.headingColor : Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontMd
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Aliases for external wiring ──
|
||||||
|
property alias waferFamilyCode: waferInfoFamilyCell.value
|
||||||
|
property alias waferSerialNumber: waferSerialCell.value
|
||||||
|
property alias waferSensorCount: waferSensorsCell.value
|
||||||
|
property alias waferRuntime: runtimeCard.value
|
||||||
|
property alias waferCycles: waferCyclesCell.value
|
||||||
|
property bool waferDetected: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return info && info.length > 0 && info[0] !== "";
|
||||||
|
}
|
||||||
|
|
||||||
// Data summary after parse
|
|
||||||
property int dataRows: 0
|
property int dataRows: 0
|
||||||
property int dataCols: 0
|
property int dataCols: 0
|
||||||
property string csvPath: ""
|
property string csvPath: ""
|
||||||
property bool dataParsed: false
|
property bool dataParsed: false
|
||||||
|
|
||||||
|
readonly property bool isConnected: deviceController.connectionStatus === "Connected"
|
||||||
|
readonly property bool isBusy: deviceController.connectionStatus.endsWith("...")
|
||||||
|
readonly property string portName: deviceController.selectedPort || "—"
|
||||||
|
|
||||||
function cleanFolderUrl(url) {
|
function cleanFolderUrl(url) {
|
||||||
return decodeURIComponent(String(url).replace(/^file:\/\//, ""));
|
return decodeURIComponent(String(url).replace(/^file:\/\//, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentFamilyCode() {
|
function currentFamilyCode() {
|
||||||
var info = deviceController.lastWaferInfo;
|
var info = deviceController.lastWaferInfo;
|
||||||
return info && info.length > 0 ? (info[0] || "") : "";
|
return info && info.length > 0 ? (info[0] || "") : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAndSavePendingRead() {
|
function parseAndSavePendingRead() {
|
||||||
deviceController.parseAndSaveData(root.currentFamilyCode(), deviceController.selectedPort || "");
|
deviceController.parseAndSaveData(root.currentFamilyCode(), deviceController.selectedPort || "");
|
||||||
}
|
}
|
||||||
@@ -48,417 +117,491 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// ROW 1: Connection Status (100%)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.preferredHeight: 90
|
||||||
spacing: Theme.rightPaneGap
|
Layout.maximumHeight: 90
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
// --- Connection Status ---
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 92
|
Layout.fillHeight: true
|
||||||
color: Theme.cardBackground
|
color: Theme.cardBackground
|
||||||
border.color: {
|
border.color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor)
|
||||||
if (deviceController.connectionStatus === "Connected")
|
border.width: 1
|
||||||
return Theme.statusSuccessColor;
|
|
||||||
if (deviceController.connectionStatus === "Disconnected")
|
|
||||||
return Theme.statusErrorColor;
|
|
||||||
return Theme.cardBorder;
|
|
||||||
}
|
|
||||||
radius: Theme.radiusSm
|
radius: Theme.radiusSm
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
id: connStatusCol
|
||||||
anchors.margins: Theme.panelPadding
|
anchors.left: parent.left
|
||||||
spacing: 4
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Theme.panelPadding
|
||||||
|
anchors.rightMargin: Theme.panelPadding
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: deviceController.connectionStatus
|
text: "CONNECTION STATUS"
|
||||||
color: {
|
color: Theme.sideMutedText
|
||||||
if (deviceController.connectionStatus === "Connected")
|
font.pixelSize: Theme.fontXs
|
||||||
return Theme.statusSuccessColor;
|
font.weight: Font.Medium
|
||||||
if (deviceController.connectionStatus === "Disconnected")
|
font.letterSpacing: 1.2
|
||||||
return Theme.statusErrorColor;
|
font.family: Theme.uiFontFamily
|
||||||
return Theme.bodyColor;
|
|
||||||
}
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Port: " + (deviceController.selectedPort || "None selected")
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontMd
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 1
|
||||||
|
Text {
|
||||||
|
text: "HOST PC"
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: "localhost"
|
||||||
|
color: Theme.headingColor
|
||||||
|
font.pixelSize: Theme.fontMd
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 22
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 4
|
||||||
|
Rectangle {
|
||||||
|
width: 4; height: 4; radius: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText)
|
||||||
|
opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: root.isConnected || root.isBusy
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation { to: 0.3; duration: 1400 }
|
||||||
|
NumberAnimation { to: 1.0; duration: 1400 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: statusPillText.implicitWidth + 16
|
||||||
|
height: 20
|
||||||
|
radius: 10
|
||||||
|
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: statusPillText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.isConnected ? "ACTIVE" : (root.isBusy ? deviceController.connectionStatus.replace("...", "").toUpperCase() : "INACTIVE")
|
||||||
|
color: "#111111"
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
font.letterSpacing: 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 4
|
||||||
|
Rectangle {
|
||||||
|
width: 4; height: 4; radius: 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText)
|
||||||
|
opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: root.isConnected || root.isBusy
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation { to: 0.3; duration: 1400 }
|
||||||
|
NumberAnimation { to: 1.0; duration: 1400 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 1
|
||||||
|
Text {
|
||||||
|
text: "COM PORT"
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: root.portName
|
||||||
|
color: root.isConnected ? Theme.statusSuccessColor : Theme.headingColor
|
||||||
|
font.pixelSize: Theme.fontMd
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 90
|
||||||
|
Layout.maximumHeight: 90
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.preferredWidth: 65
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Theme.cardBackground
|
||||||
|
border.color: Theme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.panelPadding
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "DIRECTORY"
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontMd
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.letterSpacing: 1.2
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: deviceController.saveDataDir || "Not configured"
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font.pixelSize: Theme.fontSm
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
font.italic: true
|
||||||
|
opacity: 0.7
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: browseBtn
|
||||||
|
text: "Browse"
|
||||||
|
font.pixelSize: Theme.fontSm
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
implicitHeight: 24
|
||||||
|
implicitWidth: 60
|
||||||
|
hoverEnabled: true
|
||||||
|
background: Rectangle {
|
||||||
|
color: browseBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
|
||||||
|
border.color: Theme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
}
|
||||||
|
contentItem: Text {
|
||||||
|
text: browseBtn.text
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font: browseBtn.font
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
onClicked: saveDirDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 4
|
||||||
|
visible: root.isConnected
|
||||||
|
Rectangle {
|
||||||
|
width: 6; height: 6; radius: 3
|
||||||
|
color: Theme.statusSuccessColor
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.csvPath !== ""
|
||||||
|
width: sessionSavedLabel.implicitWidth + 12
|
||||||
|
height: 18
|
||||||
|
radius: 4
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
border.color: Theme.sideBorder
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: sessionSavedLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "SESSION SAVED"
|
||||||
|
color: Theme.sideMutedText
|
||||||
|
font.pixelSize: Theme.fontXs
|
||||||
|
font.weight: Font.Bold
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
font.letterSpacing: 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Theme.cardBackground
|
||||||
|
border.color: Theme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.panelPadding
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: 6
|
||||||
|
columnSpacing: 16
|
||||||
|
|
||||||
|
GridCell {
|
||||||
|
id: waferInfoFamilyCell
|
||||||
|
label: "Family Code"
|
||||||
|
value: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return (info && info.length > 0 && info[0]) ? info[0] : "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GridCell {
|
||||||
|
id: waferCyclesCell
|
||||||
|
label: "Cycles Completed"
|
||||||
|
value: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return (info && info.length > 5 && info[5] !== undefined) ? String(info[5]) : "—";
|
||||||
|
}
|
||||||
|
isRight: true
|
||||||
|
active: root.waferDetected
|
||||||
|
}
|
||||||
|
GridCell {
|
||||||
|
id: waferSerialCell
|
||||||
|
label: "Serial"
|
||||||
|
value: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return (info && info.length > 1 && info[1]) ? info[1] : "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GridCell {
|
||||||
|
id: waferSensorsCell
|
||||||
|
label: "Sensors"
|
||||||
|
value: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return (info && info.length > 2 && info[2]) ? String(info[2]) : "—";
|
||||||
|
}
|
||||||
|
isRight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatCard {
|
||||||
|
id: runtimeCard
|
||||||
|
Layout.preferredWidth: 35
|
||||||
|
value: {
|
||||||
|
var info = deviceController.lastWaferInfo;
|
||||||
|
return (info && info.length > 4 && info[4] !== undefined) ? info[4] + "s" : "—";
|
||||||
|
}
|
||||||
|
label: "TOTAL RUNTIME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// ROW 3: Activity Log — ALWAYS visible (fills remaining height)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Theme.cardBackground
|
||||||
|
border.color: Theme.cardBorder
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.radiusMd
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
color: Theme.subtleSectionBackground
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.panelPadding
|
||||||
|
anchors.rightMargin: Theme.panelPadding
|
||||||
spacing: 8
|
spacing: 8
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Save dir: " + (deviceController.saveDataDir || "None selected")
|
text: "📄"
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
font.pixelSize: Theme.fontSm
|
||||||
opacity: 0.7
|
visible: root.csvPath !== ""
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: root.csvPath !== "" ? root.csvPath.split("/").pop() : "ACTIVITY LOG"
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font.pixelSize: root.csvPath !== "" ? Theme.fontMd : Theme.fontSm
|
||||||
|
font.letterSpacing: root.csvPath !== "" ? 0 : 1.8
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: browseBtn
|
id: refreshSessionBtn
|
||||||
text: "BROWSE..."
|
text: "Refresh"
|
||||||
font.pixelSize: Theme.font2xs
|
font.pixelSize: Theme.fontSm
|
||||||
font.weight: Font.Medium
|
font.family: Theme.uiFontFamily
|
||||||
implicitHeight: 20
|
implicitHeight: 24
|
||||||
implicitWidth: 65
|
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
|
color: refreshSessionBtn.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||||
border.color: Theme.cardBorder
|
|
||||||
border.width: 1
|
|
||||||
radius: Theme.radiusSm
|
radius: Theme.radiusSm
|
||||||
}
|
}
|
||||||
contentItem: Text {
|
contentItem: Text {
|
||||||
text: parent.text
|
text: refreshSessionBtn.text
|
||||||
color: Theme.bodyColor
|
color: Theme.bodyColor
|
||||||
font: parent.font
|
font: refreshSessionBtn.font
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
onClicked: saveDirDialog.open()
|
onClicked: deviceController.clearSession()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Wafer Info ---
|
|
||||||
GroupBox {
|
|
||||||
title: "Wafer Information"
|
|
||||||
visible: root.waferDetected
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.panelPadding
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Theme.rightPaneGap
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Family Code"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: waferInfoFamily
|
|
||||||
text: "—"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Button {
|
||||||
Layout.fillWidth: true
|
id: clearLogBtn
|
||||||
spacing: 4
|
text: "Clear"
|
||||||
Text {
|
|
||||||
text: "Serial Number"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: waferSerial
|
|
||||||
text: "—"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Sensor Count"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: waferSensors
|
|
||||||
text: "—"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Theme.rightPaneGap
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Runtime"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: waferRuntime
|
|
||||||
text: "—"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Cycles"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: waferCycles
|
|
||||||
text: "—"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Data Summary (shown after parse) ---
|
|
||||||
GroupBox {
|
|
||||||
title: "Data Summary"
|
|
||||||
visible: root.dataParsed
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.panelPadding
|
|
||||||
spacing: Theme.rightPaneGap
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Rows"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
font.pixelSize: Theme.fontSm
|
||||||
}
|
font.family: Theme.uiFontFamily
|
||||||
Text {
|
implicitHeight: 24
|
||||||
text: String(root.dataRows)
|
hoverEnabled: true
|
||||||
color: Theme.bodyColor
|
background: Rectangle {
|
||||||
font.pixelSize: Theme.fontLg
|
color: clearLogBtn.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||||
font.bold: true
|
radius: Theme.radiusSm
|
||||||
|
}
|
||||||
|
contentItem: Text {
|
||||||
|
text: clearLogBtn.text
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font: clearLogBtn.font
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
onClicked: deviceController.clearActivityLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "Sensors"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: String(root.dataCols)
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontLg
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: "CSV"
|
|
||||||
color: Theme.subheadingColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: root.csvPath
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Activity Log ---
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
color: Theme.cardBackground
|
|
||||||
border.color: Theme.cardBorder
|
|
||||||
radius: Theme.radiusMd
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// Header
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
anchors.bottom: parent.bottom
|
||||||
Layout.preferredHeight: 40
|
anchors.left: parent.left
|
||||||
color: Theme.subtleSectionBackground
|
anchors.right: parent.right
|
||||||
|
height: 1
|
||||||
RowLayout {
|
color: Theme.cardBorder
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: Theme.panelPadding
|
|
||||||
anchors.rightMargin: Theme.panelPadding
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "ACTIVITY LOG"
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: Theme.fontXs
|
|
||||||
font.letterSpacing: 1.8
|
|
||||||
font.weight: Font.Medium
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "REFRESH SESSION"
|
|
||||||
font.pixelSize: Theme.fontXs
|
|
||||||
implicitHeight: 24
|
|
||||||
hoverEnabled: true
|
|
||||||
background: Rectangle {
|
|
||||||
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
|
|
||||||
radius: Theme.radiusSm
|
|
||||||
}
|
|
||||||
contentItem: Text {
|
|
||||||
text: parent.text
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font: parent.font
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
onClicked: deviceController.clearSession()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "CLEAR LOG"
|
|
||||||
font.pixelSize: Theme.fontXs
|
|
||||||
implicitHeight: 24
|
|
||||||
hoverEnabled: true
|
|
||||||
background: Rectangle {
|
|
||||||
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
|
|
||||||
radius: Theme.radiusSm
|
|
||||||
}
|
|
||||||
contentItem: Text {
|
|
||||||
text: parent.text
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font: parent.font
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
onClicked: deviceController.clearActivityLog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
height: 1
|
|
||||||
color: Theme.cardBorder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.margins: Theme.panelPadding
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
TextArea {
|
|
||||||
id: activityLog
|
|
||||||
width: parent.width
|
|
||||||
text: qsTr("Welcome — use the side rail to detect a wafer, or import data to review.")
|
|
||||||
readOnly: true
|
|
||||||
font.family: "monospace"
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
wrapMode: TextArea.WordWrap
|
|
||||||
color: Theme.bodyColor
|
|
||||||
background: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
ScrollView {
|
||||||
target: deviceController
|
Layout.fillWidth: true
|
||||||
function onActivityLogUpdated(newLog) {
|
Layout.fillHeight: true
|
||||||
activityLog.text = newLog;
|
Layout.margins: Theme.panelPadding
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: activityLog
|
||||||
|
width: parent.width
|
||||||
|
text: qsTr("Welcome — use the side rail to detect a wafer, or import data to review.")
|
||||||
|
readOnly: true
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: Theme.fontSm
|
||||||
|
wrapMode: TextArea.WordWrap
|
||||||
|
color: Theme.bodyColor
|
||||||
|
background: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: deviceController
|
||||||
|
function onActivityLogUpdated(newLog) {
|
||||||
|
activityLog.text = newLog ? newLog : qsTr("Welcome — use the side rail to detect a wafer, or import data to review.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Signal Handlers ---
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// SIGNAL HANDLERS
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
Connections {
|
Connections {
|
||||||
target: deviceController
|
target: deviceController
|
||||||
|
|
||||||
function onLogMessage(message) {
|
function onLogMessage(message) {
|
||||||
if (message == "CHOOSE_SAVE_DIR") {
|
if (message == "CHOOSE_SAVE_DIR") {
|
||||||
saveDirDialog.open();
|
saveDirDialog.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function onPortsUpdated() {}
|
||||||
// Any operation start (detect/read/erase/debug) latches the panel on.
|
|
||||||
function onPortsUpdated() {
|
|
||||||
// Status tab always visible now
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDetectResult(result) {
|
function onDetectResult(result) {
|
||||||
if (result && result.familyCode) {
|
if (!result) {
|
||||||
root.waferDetected = true;
|
root.dataParsed = false;
|
||||||
waferInfoFamily.text = result.familyCode;
|
root.dataRows = 0;
|
||||||
waferSerial.text = result.serialNumber;
|
root.dataCols = 0;
|
||||||
waferSensors.text = String(result.sensorCount);
|
root.csvPath = "";
|
||||||
waferRuntime.text = result.runtime + "s";
|
|
||||||
waferCycles.text = String(result.cycleCount);
|
|
||||||
}
|
}
|
||||||
// On failure, keep the last displayed wafer info and do not change waferDetected.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStatusRestored() {
|
function onStatusRestored() {
|
||||||
// Show restored wafer info if available
|
|
||||||
var info = deviceController.lastWaferInfo;
|
|
||||||
if (info && info.length > 0) {
|
|
||||||
root.waferDetected = true;
|
|
||||||
waferInfoFamily.text = info[0] || "—";
|
|
||||||
waferSerial.text = info[1] || "—";
|
|
||||||
waferSensors.text = String(info[2] || 0);
|
|
||||||
waferRuntime.text = (info[3] || 0) + "s";
|
|
||||||
waferCycles.text = String(info[4] || 0);
|
|
||||||
}
|
|
||||||
// Show data summary if data was previously parsed
|
|
||||||
if (deviceController.dataRowCount > 0) {
|
if (deviceController.dataRowCount > 0) {
|
||||||
root.dataParsed = true;
|
root.dataParsed = true;
|
||||||
root.dataRows = deviceController.dataRowCount;
|
root.dataRows = deviceController.dataRowCount;
|
||||||
root.dataCols = deviceController.dataColCount;
|
root.dataCols = deviceController.dataColCount;
|
||||||
root.csvPath = ""; // CSV path not persisted as full path, just show it was parsed
|
root.csvPath = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,7 +613,6 @@ ColumnLayout {
|
|||||||
root.dataRows = 0;
|
root.dataRows = 0;
|
||||||
root.dataCols = 0;
|
root.dataCols = 0;
|
||||||
root.csvPath = "";
|
root.csvPath = "";
|
||||||
|
|
||||||
if (result && result.success === true)
|
if (result && result.success === true)
|
||||||
saveDirDialog.open();
|
saveDirDialog.open();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user