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.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ISC
|
import ISC
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
// ===== Home Workspace Shell =====
|
// ===== Home Workspace Shell =====
|
||||||
Rectangle {
|
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 ======
|
// ===== Main Two-Column Layout ======
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -106,10 +127,11 @@ Rectangle {
|
|||||||
root.selectedSideActionIndex = index
|
root.selectedSideActionIndex = index
|
||||||
root.selectedTabIndex = 0 // always jump to Status tab
|
root.selectedTabIndex = 0 // always jump to Status tab
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
root.memoryRead = false
|
if (!deviceController.saveDataDir) {
|
||||||
streamController.setMode("review")
|
saveDirDialog.open()
|
||||||
streamController.stopStream()
|
return
|
||||||
deviceController.detectWafer()
|
}
|
||||||
|
root._doDetect()
|
||||||
}
|
}
|
||||||
else if (index === 1) {
|
else if (index === 1) {
|
||||||
streamController.setMode("review")
|
streamController.setMode("review")
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ ColumnLayout {
|
|||||||
property alias waferCycles: waferCycles.text
|
property alias waferCycles: waferCycles.text
|
||||||
property bool waferDetected: false
|
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
|
// Data summary after parse
|
||||||
property int dataRows: 0
|
property int dataRows: 0
|
||||||
property int dataCols: 0
|
property int dataCols: 0
|
||||||
@@ -45,31 +40,22 @@ ColumnLayout {
|
|||||||
|
|
||||||
FolderDialog {
|
FolderDialog {
|
||||||
id: saveDirDialog
|
id: saveDirDialog
|
||||||
title: "Choose CSV Save Folder"
|
title: "Choose a folder to save."
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder))
|
deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder))
|
||||||
root.parseAndSavePendingRead()
|
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 {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
visible: root.statusActive
|
|
||||||
spacing: Theme.rightPaneGap
|
spacing: Theme.rightPaneGap
|
||||||
|
|
||||||
// --- Connection Status ---
|
// --- Connection Status ---
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 86
|
Layout.preferredHeight: 92
|
||||||
color: Theme.cardBackground
|
color: Theme.cardBackground
|
||||||
border.color: {
|
border.color: {
|
||||||
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
|
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
|
||||||
@@ -102,13 +88,44 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
RowLayout {
|
||||||
text: "Save dir: " + deviceController.saveDataDir
|
|
||||||
color: Theme.bodyColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
opacity: 0.7
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
Layout.fillWidth: true
|
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 ---
|
// --- Signal Handlers ---
|
||||||
Connections {
|
Connections {
|
||||||
target: deviceController
|
target: deviceController
|
||||||
// Any operation start (detect/read/erase/debug) latches the panel on.
|
|
||||||
function onPortsUpdated() {
|
|
||||||
if (deviceController.operationInProgress)
|
|
||||||
root.statusActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDetectResult(result) {
|
function onLogMessage(message) {
|
||||||
root.statusActive = true
|
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) {
|
if (result && result.familyCode) {
|
||||||
root.waferDetected = true
|
root.waferDetected = true
|
||||||
waferInfoFamily.text = result.familyCode
|
waferInfoFamily.text = result.familyCode
|
||||||
@@ -342,7 +365,6 @@ ColumnLayout {
|
|||||||
// Show restored wafer info if available
|
// Show restored wafer info if available
|
||||||
var info = deviceController.lastWaferInfo
|
var info = deviceController.lastWaferInfo
|
||||||
if (info && info.length > 0) {
|
if (info && info.length > 0) {
|
||||||
root.statusActive = true
|
|
||||||
root.waferDetected = true
|
root.waferDetected = true
|
||||||
waferInfoFamily.text = info[0] || "—"
|
waferInfoFamily.text = info[0] || "—"
|
||||||
waferSerial.text = info[1] || "—"
|
waferSerial.text = info[1] || "—"
|
||||||
@@ -352,7 +374,6 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
// Show data summary if data was previously parsed
|
// Show data summary if data was previously parsed
|
||||||
if (deviceController.dataRowCount > 0) {
|
if (deviceController.dataRowCount > 0) {
|
||||||
root.statusActive = true
|
|
||||||
root.dataParsed = true
|
root.dataParsed = true
|
||||||
root.dataRows = deviceController.dataRowCount
|
root.dataRows = deviceController.dataRowCount
|
||||||
root.dataCols = deviceController.dataColCount
|
root.dataCols = deviceController.dataColCount
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ class DeviceController(QObject):
|
|||||||
self._detectFinished.connect(self._handle_detect_finished, Qt.ConnectionType.QueuedConnection)
|
self._detectFinished.connect(self._handle_detect_finished, Qt.ConnectionType.QueuedConnection)
|
||||||
self._readFinished.connect(self._handle_read_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 ----
|
# ---- Properties ----
|
||||||
@Property(list, notify=portsUpdated)
|
@Property(list, notify=portsUpdated)
|
||||||
def availablePorts(self) -> list[str]:
|
def availablePorts(self) -> list[str]:
|
||||||
@@ -538,3 +542,14 @@ class DeviceController(QObject):
|
|||||||
"runtime": info.runtime,
|
"runtime": info.runtime,
|
||||||
"cycleCount": info.cycle_count,
|
"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