import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Controls.impl import ISC import ".." ColumnLayout { spacing: 6 // ── Section label & Refresh ─────────────────────────────────────────── RowLayout { Layout.fillWidth: true spacing: 4 Label { text: "SOURCE" color: Theme.sideMutedText font.family: Theme.uiFontFamily font.pixelSize: Theme.fontSm font.letterSpacing: 1.5 font.bold: true topPadding: 6 bottomPadding: 8 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Button { id: editBtn implicitWidth: 28 implicitHeight: 28 hoverEnabled: true background: Rectangle { color: editBtn.pressed ? Theme.buttonPressed : editBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: IconImage { source: "../icons/edit.svg" sourceSize.width: 16 sourceSize.height: 16 color: Theme.bodyColor } onClicked: { file_browser.refreshFiles(); csvEditorDialog.open(); } ToolTip { id: editTooltip visible: editBtn.hovered text: "Edit" delay: 400 font.pixelSize: Theme.fontXs font.family: Theme.uiFontFamily contentItem: Text { text: editTooltip.text color: Theme.headingColor font: editTooltip.font } background: Rectangle { color: Theme.cardBackground border.color: Theme.sideBorder border.width: 1 radius: Theme.radiusXs } } } Button { id: refreshBtn implicitWidth: 28 implicitHeight: 28 hoverEnabled: true background: Rectangle { color: refreshBtn.pressed ? Theme.buttonPressed : refreshBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: IconImage { source: "../icons/refresh.svg" sourceSize.width: 16 sourceSize.height: 16 color: Theme.bodyColor } onClicked: file_browser.refreshFiles() ToolTip { id: refreshTooltip visible: refreshBtn.hovered text: "Refresh" delay: 400 font.pixelSize: Theme.fontXs font.family: Theme.uiFontFamily contentItem: Text { text: refreshTooltip.text color: Theme.headingColor font: refreshTooltip.font } background: Rectangle { color: Theme.cardBackground border.color: Theme.sideBorder border.width: 1 radius: Theme.radiusXs } } } } // ── Directory button ────────────────────────────────────────────────── Button { id: dirBtn Layout.fillWidth: true hoverEnabled: true implicitHeight: 36 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: Theme.fontSm } Label { text: file_browser.currentDirectory.split("/").pop() || file_browser.currentDirectory color: Theme.headingColor font.pixelSize: Theme.fontSm elide: Text.ElideMiddle Layout.fillWidth: true } } onClicked: file_browser.chooseDirectory() } // ── Filter ──────────────────────────────────────────────────────────── TextField { id: filter Layout.fillWidth: true placeholderText: "Filter…" font.pixelSize: Theme.fontSm 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 Behavior on color { ColorAnimation { duration: Theme.durationFast } } Behavior on border.color { ColorAnimation { duration: Theme.durationFast } } } } // ── 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 highlight logic: on Data tab, highlight compareFileA/B; otherwise highlight loadedFile (for Map tab). readonly property bool isActive:{ try { if (root.selectedTabIndex === 1 && dataTabLoader.item) { return modelData.fileName === dataTabLoader.item.compareFileA || modelData.fileName === dataTabLoader.item.compareFileB; } } catch (e) {} return 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 Behavior on color { ColorAnimation { duration: Theme.durationFast } } // 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: Theme.fontMd 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: Theme.fontMd 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: Theme.fontSm } Label { visible: (modelData.timeStr || "") !== "" text: "·" color: Theme.bodyColor font.pixelSize: Theme.fontSm opacity: 0.45 } Label { visible: (modelData.timeStr || "") !== "" text: modelData.timeStr || "" color: Theme.bodyColor font.pixelSize: Theme.fontSm 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: Theme.fontXs opacity: 0.7 elide: Text.ElideRight Layout.fillWidth: true } } } } } // ── Footer ──────────────────────────────────────────────────────────── Label { text: file_browser.files.length + " files · CSV only" color: Theme.bodyColor font.pixelSize: Theme.fontSm } SelectFileDialog { id: csvEditorDialog tableModel: file_browser.files } }