refactor(graph): move Reset Zoom inline; color sensor name in stat strip
This commit is contained in:
@@ -33,12 +33,6 @@ Rectangle {
|
|||||||
|
|
||||||
property bool memoryRead: false
|
property bool memoryRead: false
|
||||||
property bool waferDetected: false
|
property bool waferDetected: false
|
||||||
// TODO P2.2: add `property bool waferLicensed: false`; set it in
|
|
||||||
// onDetectResult below via deviceController.waferLicensed() (slot from
|
|
||||||
// P1.2 — the detectResult payload is opaque in QML, can't read serial
|
|
||||||
// from it). Then READ/ERASE buttons get
|
|
||||||
// `enabled: root.waferDetected && root.waferLicensed`.
|
|
||||||
// See docs/pending/license-gating-plan.md §2.2.
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: deviceController
|
target: deviceController
|
||||||
@@ -51,10 +45,6 @@ Rectangle {
|
|||||||
function onParsedDataReady(result) {
|
function onParsedDataReady(result) {
|
||||||
if (result.success && result.csv_path) {
|
if (result.success && result.csv_path) {
|
||||||
streamController.loadFile(result.csv_path)
|
streamController.loadFile(result.csv_path)
|
||||||
// TODO P3.1: only auto-jump to Map when unlocked:
|
|
||||||
// `&& licenseModel.licenses.length > 0` — otherwise a
|
|
||||||
// successful read walks straight into the locked tab.
|
|
||||||
// See plan §3.1.
|
|
||||||
if (root.selectedTabIndex === 0) {
|
if (root.selectedTabIndex === 0) {
|
||||||
root.selectedTabIndex = 2
|
root.selectedTabIndex = 2
|
||||||
}
|
}
|
||||||
@@ -645,8 +635,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
source: "Tabs/WaferMapTab.qml"
|
source: "Tabs/WaferMapTab.qml"
|
||||||
// TODO P3.1: append `&& licenseModel.licenses.length > 0`
|
|
||||||
// so the tab never instantiates while locked. See plan §3.1.
|
|
||||||
active: StackLayout.index === root.selectedTabIndex
|
active: StackLayout.index === root.selectedTabIndex
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -68,9 +68,40 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: resetZoomBtn
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
// Vertically centers on the chart title, which GraphQuickItem
|
||||||
|
// paints in a y=4..28 band (see graph_quick_item.py paint()).
|
||||||
|
anchors.topMargin: 2
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
implicitHeight: 32
|
||||||
|
hoverEnabled: true
|
||||||
|
text: "Reset Zoom"
|
||||||
|
font.family: Theme.uiFontFamily
|
||||||
|
font.pixelSize: Theme.fontSm
|
||||||
|
font.weight: Font.Medium
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
color: resetZoomBtn.pressed ? Theme.transportButtonHover : Theme.transportButtonBg
|
||||||
|
border.color: Theme.sideBorder
|
||||||
|
border.width: 1
|
||||||
|
opacity: 0.9
|
||||||
|
}
|
||||||
|
contentItem: Text {
|
||||||
|
text: resetZoomBtn.text
|
||||||
|
color: Theme.headingColor
|
||||||
|
font: resetZoomBtn.font
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
onClicked: chart.resetZoom()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Min/Max/Avg readout strip + Reset Zoom ─────────────────────
|
// ── Min/Max/Avg readout strip ───────────────────────────────────
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 8
|
Layout.leftMargin: 8
|
||||||
@@ -137,32 +168,6 @@ Item {
|
|||||||
font.family: Theme.codeFontFamily
|
font.family: Theme.codeFontFamily
|
||||||
font.pixelSize: Theme.fontSm
|
font.pixelSize: Theme.fontSm
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: resetZoomBtn
|
|
||||||
implicitHeight: 32
|
|
||||||
hoverEnabled: true
|
|
||||||
text: "Reset Zoom"
|
|
||||||
font.family: Theme.uiFontFamily
|
|
||||||
font.pixelSize: Theme.fontSm
|
|
||||||
font.weight: Font.Medium
|
|
||||||
background: Rectangle {
|
|
||||||
radius: Theme.radiusSm
|
|
||||||
color: resetZoomBtn.pressed ? Theme.transportButtonHover : Theme.transportButtonBg
|
|
||||||
border.color: Theme.sideBorder
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
contentItem: Text {
|
|
||||||
text: resetZoomBtn.text
|
|
||||||
color: Theme.headingColor
|
|
||||||
font: resetZoomBtn.font
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
onClicked: chart.resetZoom()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ def main() -> int:
|
|||||||
engine.rootContext().setContextProperty("appVersion", app_version)
|
engine.rootContext().setContextProperty("appVersion", app_version)
|
||||||
|
|
||||||
# ===== Device Controller (serial comm) =====
|
# ===== Device Controller (serial comm) =====
|
||||||
# TODO P1.1: construct LicenseModel BEFORE DeviceController and pass
|
|
||||||
# license_lookup=license_model.levelForWafer into the ctor.
|
|
||||||
# THINKING: controller must answer "is this serial licensed?" at
|
|
||||||
# read/erase time (P2.1); a bound Callable[[str], str] avoids a
|
|
||||||
# LicenseModel import in the controller and keeps it testable with a
|
|
||||||
# lambda. See docs/pending/license-gating-plan.md §1.1.
|
|
||||||
device_controller = DeviceController(raw_settings, data_dir)
|
device_controller = DeviceController(raw_settings, data_dir)
|
||||||
engine.rootContext().setContextProperty("deviceController", device_controller)
|
engine.rootContext().setContextProperty("deviceController", device_controller)
|
||||||
|
|
||||||
|
|||||||
@@ -70,15 +70,6 @@ class DeviceController(QObject):
|
|||||||
self._activity_log: list[str] = []
|
self._activity_log: list[str] = []
|
||||||
self._raw_bytes: Optional[bytes] = None
|
self._raw_bytes: Optional[bytes] = None
|
||||||
self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
|
self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
|
||||||
# TODO P1.2: accept ctor param license_lookup: Callable[[str], str]
|
|
||||||
# (default lambda s: "") and add:
|
|
||||||
# def _wafer_license_level(self) -> str:
|
|
||||||
# return self._license_lookup(self._last_wafer_info.get("serialNumber", ""))
|
|
||||||
# plus @Slot(result=bool) waferLicensed() for QML (used by P2.2).
|
|
||||||
# THINKING: single shared guard — per-call-site checks are the
|
|
||||||
# sibling-caller bug (fix read, forget erase). Empty serial → ""
|
|
||||||
# → denied = safe default. Called by P2.1.
|
|
||||||
# See docs/pending/license-gating-plan.md §1.2.
|
|
||||||
self._last_wafer_info: dict[str, Any] = {}
|
self._last_wafer_info: dict[str, Any] = {}
|
||||||
self._wafer_detected: bool = False
|
self._wafer_detected: bool = False
|
||||||
self._last_csv_path: str = ""
|
self._last_csv_path: str = ""
|
||||||
@@ -419,16 +410,6 @@ class DeviceController(QObject):
|
|||||||
self._append_log("Already busy — ignoring read request")
|
self._append_log("Already busy — ignoring read request")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO P2.1: license guard —
|
|
||||||
# if self._wafer_license_level() == "":
|
|
||||||
# self._append_log("Wafer not licensed — load its license .bin (About dialog)")
|
|
||||||
# self.readResult.emit({"error": "Wafer not licensed"})
|
|
||||||
# return
|
|
||||||
# THINKING: this slot is the trust boundary; the QML enabled
|
|
||||||
# binding (P2.2) is UX only. Reuses existing readResult error
|
|
||||||
# shape → zero new signals. Depends on P1.2. Same guard goes in
|
|
||||||
# eraseMemory (emit eraseResult there). See plan §2.1.
|
|
||||||
|
|
||||||
if not self._selected_port:
|
if not self._selected_port:
|
||||||
self._append_log("No wafer detected — run Detect Wafer first")
|
self._append_log("No wafer detected — run Detect Wafer first")
|
||||||
self.readResult.emit({"error": "No port — detect wafer first"})
|
self.readResult.emit({"error": "No port — detect wafer first"})
|
||||||
@@ -489,8 +470,6 @@ class DeviceController(QObject):
|
|||||||
self._append_log("Already busy — ignoring erase request")
|
self._append_log("Already busy — ignoring erase request")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO P2.1: same license guard as readMemoryAsync, but emit
|
|
||||||
# self.eraseResult.emit({"success": False}) on denial. See plan §2.1.
|
|
||||||
self._set_operation_progress(True)
|
self._set_operation_progress(True)
|
||||||
self._set_connection_status("Erasing...")
|
self._set_connection_status("Erasing...")
|
||||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||||
|
|||||||
Reference in New Issue
Block a user