import QtQuick import QtQuick.Controls import QtQuick.Layouts import ISC import ".." ColumnLayout { spacing: 6 // ── Section label & Refresh ─────────────────────────────────────────── RowLayout { Layout.fillWidth: true spacing: 4 Label { text: "SOURCE" color: Theme.bodyColor font.pixelSize: 10 font.letterSpacing: 1.8 font.weight: Font.Medium topPadding: 6 bottomPadding: 8 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Button { id: editBtn text: "✎" implicitWidth: 26 implicitHeight: 26 hoverEnabled: true font.pixelSize: 14 background: Rectangle { color: editBtn.pressed ? Theme.buttonPressed : editBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: Text { text: parent.text color: Theme.bodyColor font: parent.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } onClicked: { file_browser.refreshFiles() csvEditorDialog.open() } } Button { id: refreshBtn text: "⟳" implicitWidth: 26 implicitHeight: 26 hoverEnabled: true font.pixelSize: 14 background: Rectangle { color: refreshBtn.pressed ? Theme.buttonPressed : refreshBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: Text { text: parent.text color: Theme.bodyColor font: parent.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } onClicked: file_browser.refreshFiles() } } // ── Directory button ────────────────────────────────────────────────── Button { id: dirBtn Layout.fillWidth: true hoverEnabled: true implicitHeight: 32 leftPadding: 8 rightPadding: 8 background: Rectangle { radius: Theme.radiusSm color: dirBtn.pressed ? Theme.buttonNeutralPressed : dirBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground border.width: Theme.borderThin border.color: Theme.fieldBorder } contentItem: RowLayout { spacing: 6 Label { text: "▤" color: Theme.bodyColor font.pixelSize: 12 } Label { text: file_browser.currentDirectory.split("/").pop() || file_browser.currentDirectory color: Theme.headingColor font.pixelSize: 11 elide: Text.ElideMiddle Layout.fillWidth: true } } onClicked: file_browser.chooseDirectory() } // ── Filter ──────────────────────────────────────────────────────────── TextField { id: filter Layout.fillWidth: true placeholderText: "Filter…" font.pixelSize: 11 color: Theme.fieldText placeholderTextColor: Theme.fieldPlaceholder selectedTextColor: Theme.fieldBackground selectionColor: Theme.fieldText background: Rectangle { radius: Theme.radiusXs color: Theme.fieldBackground border.width: filter.activeFocus ? Theme.borderStrong : Theme.borderThin border.color: filter.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder } } // ── File list ───────────────────────────────────────────────────────── ListView { id: fileList Layout.fillWidth: true Layout.fillHeight: true clip: true spacing: 2 highlight: null highlightFollowsCurrentItem: false model: file_browser.files delegate: ItemDelegate { id: fileItem width: ListView.view.width padding: 8 highlighted: false focusPolicy: Qt.NoFocus readonly property bool matchesFilter: { if (filter.text === "") return true var q = filter.text.toLowerCase() return (modelData.baseName + modelData.waferType + modelData.date) .toLowerCase().indexOf(q) >= 0 } // Active only in review mode; no highlight during live streaming readonly property bool isActive: streamController.mode === "review" && streamController.loadedFile !== "" && modelData.fileName === streamController.loadedFile visible: matchesFilter height: matchesFilter ? implicitHeight : 0 onClicked: streamController.loadFile(modelData.fileName) background: Rectangle { color: fileItem.isActive ? Qt.rgba(1, 1, 1, 0.06) : fileItem.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent" radius: Theme.radiusSm // Left accent bar — shown only when active Rectangle { visible: fileItem.isActive width: 3 radius: 1.5 anchors { top: parent.top; bottom: parent.bottom; left: parent.left topMargin: 5; bottomMargin: 5 } color: { var t = modelData.waferType if (t === "A" || t === "E" || t === "P") return "#3B82F6" if (t === "B" || t === "C" || t === "D") return "#10B981" if (t === "Z") return "#8B5CF6" return Theme.headingColor } } } contentItem: RowLayout { spacing: 8 // Wafer-type avatar (circle) Rectangle { implicitWidth: 28; implicitHeight: 28 Layout.preferredWidth: 28 Layout.preferredHeight: 28 Layout.alignment: Qt.AlignVCenter radius: 14 color: { var t = modelData.waferType if (t === "A" || t === "E" || t === "P") return "#1D4ED8" if (t === "B" || t === "C" || t === "D") return "#065F46" if (t === "Z") return "#7C3AED" return "#374151" } Label { anchors.centerIn: parent text: modelData.waferType || "?" font.pixelSize: 12 font.weight: Font.Bold color: "#FFFFFF" } } // Typography hierarchy ColumnLayout { spacing: 1 Layout.fillWidth: true // Primary: wafer type + serial number Label { text: modelData.serialNumber ? (modelData.waferType + modelData.serialNumber) : modelData.baseName color: Theme.headingColor font.pixelSize: 13 font.weight: Font.Bold elide: Text.ElideRight Layout.fillWidth: true } // Secondary: date · time RowLayout { spacing: 3 Layout.fillWidth: true Label { text: { var d = modelData.date || "" return d.length >= 10 ? d.substring(0, 10) : (d || "—") } color: Theme.bodyColor font.pixelSize: 10 } Label { visible: (modelData.timeStr || "") !== "" text: "·" color: Theme.bodyColor font.pixelSize: 10 opacity: 0.45 } Label { visible: (modelData.timeStr || "") !== "" text: modelData.timeStr || "" color: Theme.bodyColor font.pixelSize: 10 opacity: 0.65 } } // Tertiary: edited metadata (chamber · notes) if any Label { visible: { var c = modelData.chamber || "" var n = modelData.notes || "" return c !== "" || n !== "" } text: { var parts = [] if (modelData.chamber) parts.push(modelData.chamber) if (modelData.notes) parts.push(modelData.notes) return parts.join(" · ") } color: Theme.bodyColor font.pixelSize: 9 opacity: 0.7 elide: Text.ElideRight Layout.fillWidth: true } } } } } // ── Footer ──────────────────────────────────────────────────────────── Label { text: file_browser.files.length + " files · CSV only" color: Theme.bodyColor font.pixelSize: 10 } SelectFileDialog { id: csvEditorDialog tableModel: file_browser.files } }