Add SettingsTab, StatusTab, DataTab and wire up DeviceController
- SettingsTab: chamber ID, master CSV mapping, wafer behavior toggle, light/dark mode toggle, Edit CSV Metadata dialog - StatusTab: connection status, wafer info, activity log (blank until Detect Wafer fires) - DataTab: empty state, ready for parsed data - HomePage: side rail buttons dispatch device actions and jump to Status tab; selectedSideActionIndex defaults to -1 (nothing active on startup) - DeviceController registered as QML context property in main.py - LocalSettings: added status persistence fields - Theme: added subheadingColor and disabledText - Added serialcomm/ package and backend data/graph modules - gitignore: added build/, dist/, *.spec
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import ISC
|
||||
|
||||
// ===== Data Tab =====
|
||||
// Displays parsed temperature data in a scrollable table.
|
||||
// Column 0 = Row index, remaining columns = Sensor1, Sensor2, ...
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property int rowCount: deviceController.dataRowCount
|
||||
|
||||
// Re-layout columns whenever the model is reset
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onParsedDataReady(result) {
|
||||
if (result && result.success) {
|
||||
dataTable.forceLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Empty State =====
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 16
|
||||
visible: root.rowCount === 0
|
||||
|
||||
Label {
|
||||
text: "No Data"
|
||||
font.pixelSize: 24
|
||||
font.bold: true
|
||||
color: Theme.headingColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Read and parse wafer data to see temperature readings here."
|
||||
font.pixelSize: 14
|
||||
color: Theme.bodyColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Data Table =====
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: Theme.rightPaneGap
|
||||
visible: root.rowCount > 0
|
||||
|
||||
// --- Header row ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.rightPaneGap
|
||||
|
||||
Label {
|
||||
text: "Temperature Data"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.headingColor
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: deviceController.dataRowCount + " rows × " +
|
||||
deviceController.dataColCount + " sensors"
|
||||
font.pixelSize: 13
|
||||
color: Theme.bodyColor
|
||||
}
|
||||
}
|
||||
|
||||
// --- Table container ---
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Theme.panelBackground
|
||||
border.color: Theme.cardBorder
|
||||
border.width: Theme.borderThin
|
||||
radius: Theme.radiusSm
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
spacing: 0
|
||||
|
||||
// Column header strip
|
||||
HorizontalHeaderView {
|
||||
id: headerView
|
||||
Layout.fillWidth: true
|
||||
syncView: dataTable
|
||||
clip: true
|
||||
|
||||
delegate: Rectangle {
|
||||
implicitHeight: 28
|
||||
color: Theme.cardBackground
|
||||
border.color: Theme.cardBorder
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
// 'display' is the Qt.DisplayRole value from headerData()
|
||||
text: display !== undefined ? display : ""
|
||||
color: Theme.headingColor
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data rows
|
||||
TableView {
|
||||
id: dataTable
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: deviceController.dataModel
|
||||
clip: true
|
||||
reuseItems: true
|
||||
|
||||
columnWidthProvider: function(col) {
|
||||
if (col === 0) return 52 // row index column
|
||||
const sensorCols = Math.max(1, dataTable.columns - 1)
|
||||
const available = dataTable.width - 52
|
||||
return Math.max(52, Math.min(90, available / sensorCols))
|
||||
}
|
||||
|
||||
rowHeightProvider: function() { return 24 }
|
||||
|
||||
delegate: Rectangle {
|
||||
color: row % 2 === 0 ? Theme.panelBackground : Theme.cardBackground
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: display !== undefined ? display : ""
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import ISC
|
||||
|
||||
// ===== Status Tab =====
|
||||
// Blank on startup. Fills in after Detect Wafer fires from the left rail.
|
||||
ColumnLayout {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: Theme.rightPaneGap
|
||||
|
||||
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
|
||||
|
||||
// Data summary after parse
|
||||
property int dataRows: 0
|
||||
property int dataCols: 0
|
||||
property string csvPath: ""
|
||||
property bool dataParsed: false
|
||||
|
||||
// ===== Empty state — nothing shown until detect fires =====
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: !root.waferDetected && !deviceController.operationInProgress
|
||||
}
|
||||
|
||||
// ===== Results area (shown once detect/operation runs) =====
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: root.waferDetected || deviceController.operationInProgress
|
||||
spacing: Theme.rightPaneGap
|
||||
|
||||
// --- Connection Status ---
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 86
|
||||
color: {
|
||||
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor + "22"
|
||||
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor + "22"
|
||||
return Theme.cardBackground
|
||||
}
|
||||
border.color: {
|
||||
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
|
||||
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor
|
||||
return Theme.cardBorder
|
||||
}
|
||||
radius: Theme.radiusSm
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: 4
|
||||
|
||||
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: 16
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Port: " + (deviceController.selectedPort || "None selected")
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 14
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Save dir: " + deviceController.saveDataDir
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 12
|
||||
opacity: 0.7
|
||||
elide: Text.ElideMiddle
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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: 12 }
|
||||
Text { id: waferInfoFamily; text: "—"; color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "Serial Number"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { id: waferSerial; text: "—"; color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "Sensor Count"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { id: waferSensors; text: "—"; color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.rightPaneGap
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "Runtime"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { id: waferRuntime; text: "—"; color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "Cycles"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { id: waferCycles; text: "—"; color: Theme.bodyColor; font.pixelSize: 16; 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: 12 }
|
||||
Text { text: String(root.dataRows); color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "Sensors"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { text: String(root.dataCols); color: Theme.bodyColor; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Text { text: "CSV"; color: Theme.subheadingColor; font.pixelSize: 12 }
|
||||
Text { text: root.csvPath; color: Theme.bodyColor; font.pixelSize: 12; elide: Text.ElideMiddle }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Activity Log ---
|
||||
GroupBox {
|
||||
title: "Activity Log"
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: activityLog
|
||||
width: parent.width
|
||||
text: ""
|
||||
readOnly: true
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
wrapMode: TextArea.WordWrap
|
||||
leftPadding: 4
|
||||
rightPadding: 4
|
||||
color: Theme.bodyColor
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onActivityLogUpdated(newLog) {
|
||||
activityLog.text = newLog
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Signal Handlers ---
|
||||
Connections {
|
||||
target: deviceController
|
||||
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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onReadResult(result) {
|
||||
root.dataParsed = false
|
||||
root.dataRows = 0
|
||||
root.dataCols = 0
|
||||
root.csvPath = ""
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: deviceController
|
||||
function onParsedDataReady(result) {
|
||||
if (result && result.success) {
|
||||
root.dataParsed = true
|
||||
root.dataRows = result.rows
|
||||
root.dataCols = result.cols
|
||||
root.csvPath = result.csv_path || ""
|
||||
} else {
|
||||
root.dataParsed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user