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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user