349 lines
13 KiB
QML
349 lines
13 KiB
QML
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
|
|
}
|
|
}
|