Files
pyGUI/src/pygui/ISC/Tabs/components/SourcePanel.qml
T

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
}
}