feat(ui): implement save directory browse button and pre-detection check

- Add a 'Browse...' button to the Connection Status panel in StatusTab.qml.
- Check if save directory is set before wafer detection in HomePage.qml, prompting the user if empty.
- Resolve QML Connections warning by renaming onLogMessageEmitted to onLogMessage.
- Remove temporary debug stack traces from device_controller.py.
This commit is contained in:
jack
2026-06-18 17:08:58 -07:00
parent b417211476
commit 93868bcde4
3 changed files with 94 additions and 36 deletions
+26 -4
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import ISC
import QtQuick.Dialogs
// ===== Home Workspace Shell =====
Rectangle {
@@ -59,6 +60,26 @@ Rectangle {
}
}
function cleanFolderUrl(url) {
return decodeURIComponent(String(url).replace(/^file:\/\//, ""))
}
function _doDetect() {
root.memoryRead = false
streamController.setMode("review")
streamController.stopStream()
deviceController.detectWafer()
}
FolderDialog {
id: saveDirDialog
title: "Choose a folder to save Data"
onAccepted: {
deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder))
root._doDetect()
}
}
// ===== Main Two-Column Layout ======
RowLayout {
anchors.fill: parent
@@ -106,10 +127,11 @@ Rectangle {
root.selectedSideActionIndex = index
root.selectedTabIndex = 0 // always jump to Status tab
if (index === 0) {
root.memoryRead = false
streamController.setMode("review")
streamController.stopStream()
deviceController.detectWafer()
if (!deviceController.saveDataDir) {
saveDirDialog.open()
return
}
root._doDetect()
}
else if (index === 1) {
streamController.setMode("review")
+53 -32
View File
@@ -19,11 +19,6 @@ ColumnLayout {
property alias waferCycles: waferCycles.text
property bool waferDetected: false
// Latches true once any operation starts (or a previous session is
// restored) and never resets, so the status panel stays visible even
// when a detect finds no wafer.
property bool statusActive: false
// Data summary after parse
property int dataRows: 0
property int dataCols: 0
@@ -45,31 +40,22 @@ ColumnLayout {
FolderDialog {
id: saveDirDialog
title: "Choose CSV Save Folder"
title: "Choose a folder to save."
onAccepted: {
deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder))
root.parseAndSavePendingRead()
}
}
// ===== Empty state — nothing shown until detect fires =====
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: !root.statusActive
}
// ===== Results area (shown once detect/operation runs) =====
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.statusActive
spacing: Theme.rightPaneGap
// --- Connection Status ---
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 86
Layout.preferredHeight: 92
color: Theme.cardBackground
border.color: {
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
@@ -102,13 +88,44 @@ ColumnLayout {
Layout.fillWidth: true
}
Text {
text: "Save dir: " + deviceController.saveDataDir
color: Theme.bodyColor
font.pixelSize: 12
opacity: 0.7
elide: Text.ElideMiddle
RowLayout {
Layout.fillWidth: true
spacing: 8
Layout.alignment: Qt.AlignVCenter
Text {
text: "Save dir: " + (deviceController.saveDataDir || "None selected")
color: Theme.bodyColor
font.pixelSize: 12
opacity: 0.7
elide: Text.ElideMiddle
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
Button {
id: browseBtn
text: "BROWSE..."
font.pixelSize: 9
font.weight: Font.Medium
implicitHeight: 20
implicitWidth: 65
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
color: Theme.bodyColor
font: parent.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: saveDirDialog.open()
}
}
}
}
@@ -318,15 +335,21 @@ ColumnLayout {
// --- Signal Handlers ---
Connections {
target: deviceController
// Any operation start (detect/read/erase/debug) latches the panel on.
function onPortsUpdated() {
if (deviceController.operationInProgress)
root.statusActive = true
}
target: deviceController
function onDetectResult(result) {
root.statusActive = true
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 onDetectResult(result) {
if (result && result.familyCode) {
root.waferDetected = true
waferInfoFamily.text = result.familyCode
@@ -342,7 +365,6 @@ ColumnLayout {
// Show restored wafer info if available
var info = deviceController.lastWaferInfo
if (info && info.length > 0) {
root.statusActive = true
root.waferDetected = true
waferInfoFamily.text = info[0] || "—"
waferSerial.text = info[1] || "—"
@@ -352,7 +374,6 @@ ColumnLayout {
}
// Show data summary if data was previously parsed
if (deviceController.dataRowCount > 0) {
root.statusActive = true
root.dataParsed = true
root.dataRows = deviceController.dataRowCount
root.dataCols = deviceController.dataColCount
@@ -90,6 +90,10 @@ class DeviceController(QObject):
self._detectFinished.connect(self._handle_detect_finished, Qt.ConnectionType.QueuedConnection)
self._readFinished.connect(self._handle_read_finished, Qt.ConnectionType.QueuedConnection)
root_logger = logging.getLogger()
root_logger.addHandler(QmlActivityLogHandler(self))
root_logger.setLevel(logging.INFO)
# ---- Properties ----
@Property(list, notify=portsUpdated)
def availablePorts(self) -> list[str]:
@@ -538,3 +542,14 @@ class DeviceController(QObject):
"runtime": info.runtime,
"cycleCount": info.cycle_count,
}
class QmlActivityLogHandler(logging.Handler):
def __init__(self, controller: DeviceController) -> None:
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}"
self._controller._append_log(msg)