diff --git a/src/pygui/ISC/HomePage.qml b/src/pygui/ISC/HomePage.qml index 1be03d4..0a93416 100644 --- a/src/pygui/ISC/HomePage.qml +++ b/src/pygui/ISC/HomePage.qml @@ -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") diff --git a/src/pygui/ISC/Tabs/StatusTab.qml b/src/pygui/ISC/Tabs/StatusTab.qml index 652a828..e280ff3 100644 --- a/src/pygui/ISC/Tabs/StatusTab.qml +++ b/src/pygui/ISC/Tabs/StatusTab.qml @@ -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 diff --git a/src/pygui/backend/controllers/device_controller.py b/src/pygui/backend/controllers/device_controller.py index 2113491..73bdd00 100644 --- a/src/pygui/backend/controllers/device_controller.py +++ b/src/pygui/backend/controllers/device_controller.py @@ -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)