Compare commits
3 Commits
5105ab1ab6
...
0d5cf9e4ff
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d5cf9e4ff | |||
| 27292c3bdb | |||
| d7962f2850 |
@@ -323,6 +323,7 @@ Rectangle {
|
||||
label: "READ MEMORY"
|
||||
iconSource: "../icons/read.svg"
|
||||
Layout.fillWidth: true
|
||||
enabled: root.waferDetected
|
||||
onClicked: deviceController.readMemoryAsync()
|
||||
}
|
||||
|
||||
@@ -331,6 +332,7 @@ Rectangle {
|
||||
iconSource: "../icons/erase.svg"
|
||||
Layout.fillWidth: true
|
||||
destructive: true
|
||||
enabled: root.waferDetected
|
||||
onClicked: deviceController.eraseMemory(
|
||||
deviceController.selectedPort)
|
||||
}
|
||||
|
||||
+504
-362
@@ -4,37 +4,106 @@ import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
import ISC
|
||||
|
||||
// ===== Status Tab =====
|
||||
// Shows connection status, wafer info, and activity log.
|
||||
// Initially visible — displays connection status and activity log from start.
|
||||
// ===== Status Tab — Bento Grid Dashboard =====
|
||||
// ROW 1: Connection Status (65%) | Cycles Completed (35%)
|
||||
// ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%)
|
||||
// ROW 3: Activity Log (100%, fills remaining)
|
||||
ColumnLayout {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: Theme.rightPaneGap
|
||||
spacing: 8
|
||||
|
||||
property alias waferFamilyCode: waferInfoFamily.text
|
||||
property alias waferSerialNumber: waferSerial.text
|
||||
property alias waferSensorCount: waferSensors.text
|
||||
property alias waferRuntime: waferRuntime.text
|
||||
property alias waferCycles: waferCycles.text
|
||||
property bool waferDetected: false
|
||||
// ── Reusable Inline Components ──
|
||||
component StatCard : Rectangle {
|
||||
property alias value: valueText.text
|
||||
property alias label: labelText.text
|
||||
property bool active: root.waferDetected
|
||||
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 dataCols: 0
|
||||
property string csvPath: ""
|
||||
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) {
|
||||
return decodeURIComponent(String(url).replace(/^file:\/\//, ""));
|
||||
}
|
||||
|
||||
function currentFamilyCode() {
|
||||
var info = deviceController.lastWaferInfo;
|
||||
return info && info.length > 0 ? (info[0] || "") : "";
|
||||
}
|
||||
|
||||
function parseAndSavePendingRead() {
|
||||
deviceController.parseAndSaveData(root.currentFamilyCode(), deviceController.selectedPort || "");
|
||||
}
|
||||
@@ -48,417 +117,491 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// ROW 1: Connection Status (100%)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: Theme.rightPaneGap
|
||||
Layout.preferredHeight: 90
|
||||
Layout.maximumHeight: 90
|
||||
spacing: 8
|
||||
|
||||
// --- Connection Status ---
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 92
|
||||
Layout.fillHeight: true
|
||||
color: Theme.cardBackground
|
||||
border.color: {
|
||||
if (deviceController.connectionStatus === "Connected")
|
||||
return Theme.statusSuccessColor;
|
||||
if (deviceController.connectionStatus === "Disconnected")
|
||||
return Theme.statusErrorColor;
|
||||
return Theme.cardBorder;
|
||||
}
|
||||
border.color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor)
|
||||
border.width: 1
|
||||
radius: Theme.radiusSm
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: 4
|
||||
id: connStatusCol
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.panelPadding
|
||||
anchors.rightMargin: Theme.panelPadding
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: deviceController.connectionStatus
|
||||
color: {
|
||||
if (deviceController.connectionStatus === "Connected")
|
||||
return Theme.statusSuccessColor;
|
||||
if (deviceController.connectionStatus === "Disconnected")
|
||||
return Theme.statusErrorColor;
|
||||
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
|
||||
text: "CONNECTION STATUS"
|
||||
color: Theme.sideMutedText
|
||||
font.pixelSize: Theme.fontXs
|
||||
font.weight: Font.Medium
|
||||
font.letterSpacing: 1.2
|
||||
font.family: Theme.uiFontFamily
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
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
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
text: "Save dir: " + (deviceController.saveDataDir || "None selected")
|
||||
color: Theme.bodyColor
|
||||
text: "📄"
|
||||
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
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
id: browseBtn
|
||||
text: "BROWSE..."
|
||||
font.pixelSize: Theme.font2xs
|
||||
font.weight: Font.Medium
|
||||
implicitHeight: 20
|
||||
implicitWidth: 65
|
||||
id: refreshSessionBtn
|
||||
text: "Refresh"
|
||||
font.pixelSize: Theme.fontSm
|
||||
font.family: Theme.uiFontFamily
|
||||
implicitHeight: 24
|
||||
hoverEnabled: true
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
|
||||
border.color: Theme.cardBorder
|
||||
border.width: 1
|
||||
color: refreshSessionBtn.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||
radius: Theme.radiusSm
|
||||
}
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
text: refreshSessionBtn.text
|
||||
color: Theme.bodyColor
|
||||
font: parent.font
|
||||
font: refreshSessionBtn.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
onClicked: saveDirDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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
|
||||
}
|
||||
onClicked: deviceController.clearSession()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
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
|
||||
Button {
|
||||
id: clearLogBtn
|
||||
text: "Clear"
|
||||
font.pixelSize: Theme.fontSm
|
||||
}
|
||||
Text {
|
||||
text: String(root.dataRows)
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: Theme.fontLg
|
||||
font.bold: true
|
||||
font.family: Theme.uiFontFamily
|
||||
implicitHeight: 24
|
||||
hoverEnabled: true
|
||||
background: Rectangle {
|
||||
color: clearLogBtn.hovered ? Theme.buttonNeutralHover : "transparent"
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
color: Theme.subtleSectionBackground
|
||||
|
||||
RowLayout {
|
||||
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
|
||||
}
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: Theme.cardBorder
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onActivityLogUpdated(newLog) {
|
||||
activityLog.text = newLog;
|
||||
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 {
|
||||
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 {
|
||||
target: deviceController
|
||||
|
||||
function onLogMessage(message) {
|
||||
if (message == "CHOOSE_SAVE_DIR") {
|
||||
saveDirDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
// Any operation start (detect/read/erase/debug) latches the panel on.
|
||||
function onPortsUpdated() {
|
||||
// Status tab always visible now
|
||||
}
|
||||
|
||||
function onPortsUpdated() {}
|
||||
function onDetectResult(result) {
|
||||
if (result && result.familyCode) {
|
||||
root.waferDetected = true;
|
||||
waferInfoFamily.text = result.familyCode;
|
||||
waferSerial.text = result.serialNumber;
|
||||
waferSensors.text = String(result.sensorCount);
|
||||
waferRuntime.text = result.runtime + "s";
|
||||
waferCycles.text = String(result.cycleCount);
|
||||
if (!result) {
|
||||
root.dataParsed = false;
|
||||
root.dataRows = 0;
|
||||
root.dataCols = 0;
|
||||
root.csvPath = "";
|
||||
}
|
||||
// On failure, keep the last displayed wafer info and do not change waferDetected.
|
||||
}
|
||||
|
||||
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) {
|
||||
root.dataParsed = true;
|
||||
root.dataRows = deviceController.dataRowCount;
|
||||
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.dataCols = 0;
|
||||
root.csvPath = "";
|
||||
|
||||
if (result && result.success === true)
|
||||
saveDirDialog.open();
|
||||
}
|
||||
|
||||
@@ -246,29 +246,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Body: 3 zones (TODO P2.1: becomes 2 zones) ──────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// TODO P2.1: Delete the SourcePanel Rectangle (lines ~219-233) —
|
||||
// the file list moved to the rail (P1.3 Map context panel).
|
||||
//
|
||||
// THINKING:
|
||||
// SourcePanel shows a file list driven by file_browser — now that
|
||||
// the Map tab's context panel in the rail (P1.3) provides the same
|
||||
// file list with search/filter, SourcePanel in the wafer body is
|
||||
// redundant. The wafer map gets more horizontal space.
|
||||
//
|
||||
// After deletion: RowLayout has 2 children instead of 3.
|
||||
// Add Layout.fillWidth: true to the center ColumnLayout so it
|
||||
// fills the vacated width.
|
||||
//
|
||||
// Cross-ref: P1.3 (Map context panel), P2.1 (this task)
|
||||
// -------------------------------------------------------------------
|
||||
// ── Body: 2 columns (wafer + trend + transport | readout) ──────────
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 16
|
||||
|
||||
// Source panel — moved to side rail (P2.1)
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -281,6 +264,7 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
blend: readoutPanel.heatmapBlend
|
||||
showLabels: readoutPanel.showLabels
|
||||
showThickness: readoutPanel.showThickness
|
||||
}
|
||||
Rectangle {
|
||||
id: trendPane
|
||||
|
||||
@@ -11,6 +11,7 @@ ColumnLayout {
|
||||
property var s: streamController.stats
|
||||
property alias showLabels: labelsToggle.checked
|
||||
property alias heatmapBlend: heatmapSlider.value
|
||||
property alias showThickness: thicknessToggle.checked
|
||||
|
||||
component PanelCheckBox: CheckBox {
|
||||
id: toggle
|
||||
@@ -333,7 +334,6 @@ ColumnLayout {
|
||||
text: "Show Thickness"
|
||||
checked: false
|
||||
font.pixelSize: Theme.fontXs
|
||||
onCheckedChanged: waferMapItem.showThickness = checked
|
||||
}
|
||||
|
||||
PanelCheckBox {
|
||||
|
||||
@@ -47,15 +47,16 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
id: editTooltip
|
||||
visible: editBtn.hovered
|
||||
text: "Edit"
|
||||
delay: 400
|
||||
font.pixelSize: Theme.fontXs
|
||||
font.family: Theme.uiFontFamily
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
text: editTooltip.text
|
||||
color: Theme.headingColor
|
||||
font: parent.font
|
||||
font: editTooltip.font
|
||||
}
|
||||
background: Rectangle {
|
||||
color: Theme.cardBackground
|
||||
@@ -84,15 +85,16 @@ ColumnLayout {
|
||||
onClicked: file_browser.refreshFiles()
|
||||
|
||||
ToolTip {
|
||||
id: refreshTooltip
|
||||
visible: refreshBtn.hovered
|
||||
text: "Refresh"
|
||||
delay: 400
|
||||
font.pixelSize: Theme.fontXs
|
||||
font.family: Theme.uiFontFamily
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
text: refreshTooltip.text
|
||||
color: Theme.headingColor
|
||||
font: parent.font
|
||||
font: refreshTooltip.font
|
||||
}
|
||||
background: Rectangle {
|
||||
color: Theme.cardBackground
|
||||
|
||||
@@ -7,6 +7,7 @@ Item {
|
||||
id: root
|
||||
property real blend: 0.0
|
||||
property bool showLabels: true
|
||||
property alias showThickness: map.showThickness
|
||||
|
||||
WaferMapItem {
|
||||
id: map
|
||||
|
||||
@@ -40,10 +40,10 @@ class DeviceController(QObject):
|
||||
# ---- public signals ----
|
||||
portsUpdated = Signal(list)
|
||||
detectResult = Signal(object) # WaferInfo dict or None
|
||||
readResult = Signal(object) # {"success": bool, "bytes": int} or {"error": str}
|
||||
eraseResult = Signal(object) # {"success": bool}
|
||||
debugResult = Signal(object) # {"success": bool, "sensor_bytes": int, "debug_bytes": int}
|
||||
parsedDataReady = Signal(object) # {"success": bool, "rows": int, "cols": int, "data": ..., "csv_path": str}
|
||||
readResult = Signal(dict) # {"success": bool, "bytes": int} or {"error": str}
|
||||
eraseResult = Signal(dict) # {"success": bool}
|
||||
debugResult = Signal(dict) # {"success": bool, "sensor_bytes": int, "debug_bytes": int}
|
||||
parsedDataReady = Signal(dict) # {"success": bool, "rows": int, "cols": int, "data": ..., "csv_path": str}
|
||||
statusRestored = Signal() # Emitted when status is restored from previous session
|
||||
logMessage = Signal(str)
|
||||
activityLogUpdated = Signal(str)
|
||||
@@ -58,35 +58,21 @@ class DeviceController(QObject):
|
||||
self._data_dir = data_dir
|
||||
self._service = DeviceService(settings)
|
||||
|
||||
# Always start fresh — never restore connection status from a prior session
|
||||
# Always start fresh — never restore connection status or logs from a prior session
|
||||
self._connection_status = "Disconnected"
|
||||
self._operation_in_progress = False
|
||||
self._activity_log: list[str] = getattr(settings, 'activity_log', [])
|
||||
self._activity_log: list[str] = []
|
||||
self._raw_bytes: Optional[bytes] = None
|
||||
from pathlib import Path
|
||||
self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
|
||||
self._last_wafer_info: dict[str, Any] = getattr(settings, 'last_wafer_info', {})
|
||||
self._last_csv_path: str = getattr(settings, 'last_csv_path', "")
|
||||
self._selected_port: str = getattr(settings, 'selected_port', "")
|
||||
self._data_row_count: int = getattr(settings, 'data_row_count', 0)
|
||||
self._data_col_count: int = getattr(settings, 'data_col_count', 0)
|
||||
self._last_wafer_info: dict[str, Any] = {}
|
||||
self._last_csv_path: str = ""
|
||||
self._selected_port: str = ""
|
||||
self._data_row_count: int = 0
|
||||
self._data_col_count: int = 0
|
||||
self._data_model = TemperatureTableModel(self)
|
||||
self._graph_view = GraphView(self)
|
||||
|
||||
# If we have persisted activity log, emit it to QML
|
||||
if self._activity_log:
|
||||
self.activityLogUpdated.emit("\n".join(self._activity_log))
|
||||
|
||||
# Log status restoration and emit signal for UI updates
|
||||
if self._connection_status != "Disconnected" or self._selected_port or self._last_wafer_info:
|
||||
self._append_log("Restored previous session status")
|
||||
if self._selected_port:
|
||||
self._append_log(f"Last selected port: {self._selected_port}")
|
||||
if self._last_wafer_info:
|
||||
info = self._last_wafer_info
|
||||
self._append_log(f"Last wafer: {info.get('serialNumber', 'Unknown')} ({info.get('familyCode', 'Unknown')})")
|
||||
self.statusRestored.emit()
|
||||
|
||||
# Reset connection status on fresh start
|
||||
self._connection_status = "Disconnected"
|
||||
|
||||
@@ -162,10 +148,17 @@ class DeviceController(QObject):
|
||||
Returns [familyCode, serialNumber, sensorCount, mfgDateHex, runtime, cycleCount].
|
||||
"""
|
||||
info = self._last_wafer_info
|
||||
family_code = info.get("familyCode", "")
|
||||
sensor_count = info.get("sensorCount", 0)
|
||||
from pygui.serialcomm.data_parser import csv_column_count
|
||||
mapped_count = csv_column_count(family_code)
|
||||
if mapped_count > 0:
|
||||
sensor_count = mapped_count
|
||||
|
||||
return [
|
||||
info.get("familyCode", ""),
|
||||
family_code,
|
||||
info.get("serialNumber", ""),
|
||||
info.get("sensorCount", 0),
|
||||
sensor_count,
|
||||
info.get("mfgDateHex", ""),
|
||||
info.get("runtime", 0),
|
||||
info.get("cycleCount", 0),
|
||||
@@ -239,9 +232,13 @@ class DeviceController(QObject):
|
||||
self._data_col_count = 0
|
||||
self._raw_bytes = None
|
||||
self._operation_in_progress = False
|
||||
self._data_model.reset()
|
||||
self._graph_view.resetChart()
|
||||
self._activity_log.clear()
|
||||
self.detectResult.emit(None)
|
||||
self.parsedDataReady.emit({"success": False})
|
||||
self.portsUpdated.emit(self.availablePorts)
|
||||
self._append_log("Session refreshed/cleared")
|
||||
self.activityLogUpdated.emit("")
|
||||
self._save_status()
|
||||
|
||||
|
||||
@@ -280,6 +277,18 @@ class DeviceController(QObject):
|
||||
self._append_log("Already busy — ignoring detect request")
|
||||
return
|
||||
|
||||
# Clear previous session data & logs for a fresh session
|
||||
self._selected_port = ""
|
||||
self._last_wafer_info = {}
|
||||
self._data_row_count = 0
|
||||
self._data_col_count = 0
|
||||
self._raw_bytes = None
|
||||
self._data_model.reset()
|
||||
self._graph_view.resetChart()
|
||||
self._activity_log.clear()
|
||||
self.detectResult.emit(None)
|
||||
self.parsedDataReady.emit({"success": False})
|
||||
|
||||
self._set_operation_progress(True)
|
||||
self._set_connection_status("Detecting...")
|
||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||
@@ -306,10 +315,6 @@ class DeviceController(QObject):
|
||||
if info is not None:
|
||||
self._selected_port = port or ""
|
||||
self._set_connection_status("Connected")
|
||||
self._append_log(
|
||||
f"Detected: {info.serial_number} (family={info.family_code}, "
|
||||
f"port={port}, runtime={info.runtime}s, cycles={info.cycle_count})"
|
||||
)
|
||||
wafer_dict = self._wafer_info_to_dict(info)
|
||||
self._last_wafer_info = wafer_dict
|
||||
self.detectResult.emit(wafer_dict)
|
||||
@@ -558,10 +563,16 @@ class DeviceController(QObject):
|
||||
@staticmethod
|
||||
def _wafer_info_to_dict(info: WaferInfo) -> dict[str, Any]:
|
||||
"""Convert WaferInfo dataclass to JSON-serialisable dict for QML."""
|
||||
from pygui.serialcomm.data_parser import csv_column_count
|
||||
sensor_count = info.sensor_count
|
||||
mapped_count = csv_column_count(info.family_code)
|
||||
if mapped_count > 0:
|
||||
sensor_count = mapped_count
|
||||
|
||||
return {
|
||||
"familyCode": info.family_code,
|
||||
"serialNumber": info.serial_number,
|
||||
"sensorCount": info.sensor_count,
|
||||
"sensorCount": sensor_count,
|
||||
"mfgDateHex": info.mfg_date_hex,
|
||||
"runtime": info.runtime,
|
||||
"cycleCount": info.cycle_count,
|
||||
@@ -572,8 +583,9 @@ class QmlActivityLogHandler(logging.Handler):
|
||||
super().__init__()
|
||||
self._controller = controller
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
level = record.levelname
|
||||
message = record.getMessage()
|
||||
msg = f"[{timestamp}] {level}: {message}"
|
||||
if "found wafer on" in message:
|
||||
return
|
||||
level = record.levelname
|
||||
msg = f"{level}: {message}"
|
||||
self._controller._append_log(msg)
|
||||
|
||||
@@ -140,3 +140,50 @@ def test_logging_handler(controller):
|
||||
logger = logging.getLogger("test_logger")
|
||||
logger.info("Hello QML Activity Log")
|
||||
assert any("Hello QML Activity Log" in line for line in controller._activity_log)
|
||||
|
||||
def test_fresh_session_on_startup(mock_settings):
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
# Configure mock settings with some data to simulate previous session
|
||||
mock_settings.activity_log = ["[12:00:00] Old message"]
|
||||
mock_settings.selected_port = "COM9"
|
||||
mock_settings.last_wafer_info = {"familyCode": "P", "serialNumber": "P00002"}
|
||||
mock_settings.data_row_count = 50
|
||||
mock_settings.data_col_count = 244
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
new_controller = DeviceController(mock_settings, temp_dir)
|
||||
# Should start completely fresh (empty/default) ignoring the persisted status
|
||||
assert len(new_controller._activity_log) == 0
|
||||
assert new_controller.selectedPort == ""
|
||||
assert new_controller.lastWaferInfo == ["", "", 0, "", 0, 0]
|
||||
assert new_controller.dataRowCount == 0
|
||||
assert new_controller.dataColCount == 0
|
||||
finally:
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
def test_detect_wafer_clears_old_session(controller):
|
||||
controller._selected_port = "COM1"
|
||||
controller._last_wafer_info = {"familyCode": "P", "serialNumber": "P00001"}
|
||||
controller._data_row_count = 10
|
||||
controller._data_col_count = 244
|
||||
controller._activity_log.append("[12:00:00] Previous message")
|
||||
|
||||
# We mock detect_all_ports to not run long or fail
|
||||
controller._service.detect_all_ports = MagicMock(return_value=(None, None))
|
||||
|
||||
with patch("threading.Thread") as mock_thread:
|
||||
controller.detectWafer()
|
||||
|
||||
# Verify it cleared all session variables and logs immediately at start of detect
|
||||
assert controller.selectedPort == ""
|
||||
assert controller.lastWaferInfo == ["", "", 0, "", 0, 0]
|
||||
assert controller.dataRowCount == 0
|
||||
assert controller.dataColCount == 0
|
||||
|
||||
# Log should only contain "Scanning all ports for wafer ..."
|
||||
assert len(controller._activity_log) == 1
|
||||
assert "Scanning all ports" in controller._activity_log[0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user