SettingTab #1
+63
-10
@@ -3,24 +3,33 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ISC
|
import ISC
|
||||||
|
|
||||||
|
// ===== Home Workspace Shell =====
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Theme.pageBackground
|
color: Theme.pageBackground
|
||||||
clip: true
|
clip: true
|
||||||
|
border.color: Theme.outerFrameBorder
|
||||||
|
border.width: Theme.borderStrong
|
||||||
|
|
||||||
|
// ===== Navigation Model =====
|
||||||
// Primary navigation shown in the left rail.
|
// Primary navigation shown in the left rail.
|
||||||
property var sideActions: ["DETECT WAFER", "READ MEMORY", "OPEN CSV IN EXCEL", "ERASE MEMORY", "IMPORT DATA", "STORED DATA"]
|
property var sideActions: ["DETECT WAFER", "READ MEMORY", "OPEN CSV IN EXCEL", "ERASE MEMORY", "IMPORT DATA", "STORED DATA"]
|
||||||
|
|
||||||
|
// ===== Footer Tab Model =====
|
||||||
// Footer tabs drive the active workspace section.
|
// Footer tabs drive the active workspace section.
|
||||||
property var bottomTabs: ["Status", "Graph", "Data", "Wafer Map", "Compare", "Split", "Settings", "About"]
|
property var bottomTabs: ["Status", "Graph", "Data", "Wafer Map", "Compare", "Split", "Settings", "About"]
|
||||||
|
|
||||||
|
// ===== View State =====
|
||||||
property int selectedTabIndex: 0
|
property int selectedTabIndex: 0
|
||||||
|
property int selectedSideActionIndex: 0
|
||||||
|
|
||||||
|
// ===== Main Two-Column Layout =====
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
// ===== Left Action Rail =====
|
||||||
// Left control rail.
|
// Left control rail.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: sideRail
|
id: sideRail
|
||||||
@@ -28,7 +37,7 @@ Rectangle {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
color: Theme.sideRailBackground
|
color: Theme.sideRailBackground
|
||||||
border.color: Theme.workspaceBorder
|
border.color: Theme.workspaceBorder
|
||||||
border.width: 1
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
readonly property int actionCount: root.sideActions.length
|
readonly property int actionCount: root.sideActions.length
|
||||||
readonly property real computedButtonHeight: Math.min(Theme.sideButtonHeight, (height - (Theme.panelPadding * 2) - (Theme.sideRailSpacing * Math.max(0, actionCount - 1))) / Math.max(1, actionCount))
|
readonly property real computedButtonHeight: Math.min(Theme.sideButtonHeight, (height - (Theme.panelPadding * 2) - (Theme.sideRailSpacing * Math.max(0, actionCount - 1))) / Math.max(1, actionCount))
|
||||||
@@ -44,20 +53,41 @@ Rectangle {
|
|||||||
Button {
|
Button {
|
||||||
id: control
|
id: control
|
||||||
text: modelData
|
text: modelData
|
||||||
|
property bool isActive: index === root.selectedSideActionIndex
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Math.max(Theme.sideButtonMinHeight, sideRail.computedButtonHeight)
|
Layout.preferredHeight: Math.max(Theme.sideButtonMinHeight, sideRail.computedButtonHeight)
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: root.selectedSideActionIndex = index
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: control.down ? Theme.buttonPressed : Theme.cardBackground
|
color: {
|
||||||
border.color: Theme.cardBorder
|
if (control.down) {
|
||||||
|
return Theme.buttonPressed;
|
||||||
|
}
|
||||||
|
if (control.isActive) {
|
||||||
|
return Theme.sideActiveBackground;
|
||||||
|
}
|
||||||
|
return "transparent";
|
||||||
|
}
|
||||||
|
border.color: control.isActive ? Theme.cardBorder : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
radius: 4
|
radius: Theme.radiusSm
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: 3
|
||||||
|
visible: control.isActive
|
||||||
|
color: Theme.primaryAccent
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Text {
|
contentItem: Text {
|
||||||
text: control.text
|
text: control.text
|
||||||
color: Theme.headingColor
|
color: control.isActive ? Theme.headingColor : Theme.bodyColor
|
||||||
font.bold: true
|
font.bold: control.isActive
|
||||||
font.pixelSize: 18
|
font.pixelSize: 18
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -74,13 +104,14 @@ Rectangle {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
color: Theme.workspaceBackground
|
color: Theme.workspaceBackground
|
||||||
border.color: Theme.workspaceBorder
|
border.color: Theme.workspaceBorder
|
||||||
border.width: 1
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.mainAreaPadding
|
anchors.margins: Theme.mainAreaPadding
|
||||||
spacing: Theme.rightPaneGap
|
spacing: Theme.rightPaneGap
|
||||||
|
|
||||||
|
// ===== Active Tab Content Area =====
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
@@ -88,23 +119,45 @@ Rectangle {
|
|||||||
color: Theme.responseBackground
|
color: Theme.responseBackground
|
||||||
border.color: Theme.responseBorder
|
border.color: Theme.responseBorder
|
||||||
border.width: 1
|
border.width: 1
|
||||||
radius: 8
|
radius: Theme.radiusMd
|
||||||
|
|
||||||
|
StackLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
currentIndex: root.selectedTabIndex
|
||||||
|
|
||||||
|
// ===== Tab Content Routing =====
|
||||||
|
Repeater {
|
||||||
|
model: root.bottomTabs
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property string tabName: modelData
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: parent.tabName === "Settings"
|
||||||
|
source: parent.tabName === "Settings" ? "Tabs/SettingsTab.qml" : ""
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Main content area"
|
visible: parent.tabName !== "Settings"
|
||||||
|
text: parent.tabName + " content"
|
||||||
color: Theme.bodyColor
|
color: Theme.bodyColor
|
||||||
font.pixelSize: 20
|
font.pixelSize: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep the tab strip evenly distributed across the footer.
|
// Keep the tab strip evenly distributed across the footer.
|
||||||
|
// ===== Footer Tab Strip =====
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Theme.tabBarHeight + (Theme.tabBarPadding * 2)
|
Layout.preferredHeight: Theme.tabBarHeight + (Theme.tabBarPadding * 2)
|
||||||
color: Theme.tabBarBackground
|
color: Theme.tabBarBackground
|
||||||
border.color: Theme.workspaceBorder
|
border.color: Theme.workspaceBorder
|
||||||
border.width: 1
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -0,0 +1,553 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Qt.labs.qmlmodels
|
||||||
|
import ISC
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: root
|
||||||
|
title: "Select File"
|
||||||
|
width: 980
|
||||||
|
height: 640
|
||||||
|
modal: true
|
||||||
|
padding: 16
|
||||||
|
topPadding: 0
|
||||||
|
parent: Overlay.overlay
|
||||||
|
x: Math.round(((parent ? parent.width : width) - width) / 2)
|
||||||
|
y: Math.round(((parent ? parent.height : height) - height) / 2)
|
||||||
|
|
||||||
|
signal fileChosen(string path)
|
||||||
|
|
||||||
|
// ===== Dialog State =====
|
||||||
|
property string selectedPath: ""
|
||||||
|
property var tableModel: []
|
||||||
|
property int selectedRow: -1
|
||||||
|
property int metadataEditRow: -1
|
||||||
|
|
||||||
|
function openMetadataEditor(row) {
|
||||||
|
metadataEditRow = row;
|
||||||
|
const rec = tableModel[row];
|
||||||
|
if (!rec) {
|
||||||
|
metadataEditDialog.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
waferField.text = rec.wafer ?? "";
|
||||||
|
dateField.text = rec.date ?? "";
|
||||||
|
chamberField.text = rec.chamber ?? "";
|
||||||
|
notesField.text = rec.notes ?? "";
|
||||||
|
selectedField.checked = rec.selected ?? false;
|
||||||
|
filePathLabel.text = rec.fileName ?? "";
|
||||||
|
metadataEditDialog.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMetadataFromEditor() {
|
||||||
|
if (metadataEditRow < 0) return;
|
||||||
|
const rec = tableModel[metadataEditRow];
|
||||||
|
if (!rec) {
|
||||||
|
metadataEditDialog.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file_browser.saveMetadata(rec.fileName ?? "", waferField.text, dateField.text, chamberField.text, notesField.text, selectedField.checked);
|
||||||
|
metadataEditDialog.close();
|
||||||
|
metadataEditRow = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var headerTitles: ["", "Wafer", "Date", "Chamber", "Notes", "File", "Edit", "Save"]
|
||||||
|
readonly property var normalizedRows: (tableModel || []).map(function (row) {
|
||||||
|
row = row || {};
|
||||||
|
return {
|
||||||
|
select: row.selected ?? false,
|
||||||
|
wafer: row.wafer ?? "",
|
||||||
|
date: row.date ?? "",
|
||||||
|
chamber: row.chamber ?? "",
|
||||||
|
edit: "",
|
||||||
|
save: "",
|
||||||
|
notes: row.notes ?? "",
|
||||||
|
fileName: row.fileName ?? "",
|
||||||
|
highlight: row.highlight ?? false
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
// ===== Reusable Controls =====
|
||||||
|
component DialogActionButton: Button {
|
||||||
|
id: dialogButton
|
||||||
|
hoverEnabled: true
|
||||||
|
implicitHeight: 36
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
color: dialogButton.down ? Theme.buttonNeutralPressed
|
||||||
|
: dialogButton.hovered ? Theme.buttonNeutralHover
|
||||||
|
: Theme.buttonNeutralBackground
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: dialogButton.text
|
||||||
|
color: Theme.buttonNeutralText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component DialogTextInput: TextField {
|
||||||
|
id: dialogTextInput
|
||||||
|
color: Theme.fieldText
|
||||||
|
placeholderTextColor: Theme.fieldPlaceholder
|
||||||
|
selectedTextColor: Theme.fieldBackground
|
||||||
|
selectionColor: Theme.fieldText
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
border.width: dialogTextInput.activeFocus ? Theme.borderStrong : Theme.borderThin
|
||||||
|
border.color: dialogTextInput.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Dialog Chrome =====
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusLg
|
||||||
|
color: Theme.panelBackground
|
||||||
|
border.color: Theme.outerFrameBorder
|
||||||
|
border.width: Theme.borderStrong
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom header — replaces the native dark title bar
|
||||||
|
header: Rectangle {
|
||||||
|
width: root.width
|
||||||
|
height: 48
|
||||||
|
radius: Theme.radiusLg
|
||||||
|
color: Theme.subtleSectionBackground
|
||||||
|
border.color: Theme.innerFrameBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
|
// Square off the bottom corners
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: parent.radius
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
text: root.title
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.headingColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
DialogActionButton {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
text: "✕"
|
||||||
|
implicitWidth: 32
|
||||||
|
implicitHeight: 32
|
||||||
|
onClicked: root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Content =====
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
// ===== Browser Toolbar =====
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: "Choose Folder"
|
||||||
|
Layout.preferredWidth: 140
|
||||||
|
onClicked: file_browser.chooseDirectory()
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: "Refresh"
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
onClicked: file_browser.refreshFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
Layout.minimumWidth: 80
|
||||||
|
clip: true
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: file_browser.currentDirectory
|
||||||
|
color: Theme.fieldText
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== File Table =====
|
||||||
|
HorizontalHeaderView {
|
||||||
|
id: headerView
|
||||||
|
syncView: tableView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
implicitHeight: 34
|
||||||
|
color: Theme.subtleSectionBackground
|
||||||
|
border.color: Theme.innerFrameBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.headerTitles[index] ?? ""
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Theme.headingColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableView {
|
||||||
|
id: tableView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
columnSpacing: 0
|
||||||
|
rowSpacing: 0
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
columnWidthProvider: function (col) {
|
||||||
|
// [select, wafer, date, chamber, notes, file (variable), edit, save]
|
||||||
|
const fixed = { 0: 40, 1: 90, 2: 170, 3: 120, 4: 180, 6: 56, 7: 56 };
|
||||||
|
if (fixed.hasOwnProperty(col)) return fixed[col];
|
||||||
|
// col 5 (File) takes remaining width
|
||||||
|
const w = Math.max(400, tableView.width);
|
||||||
|
const used = 40 + 90 + 170 + 120 + 180 + 56 + 56;
|
||||||
|
return Math.max(120, w - used);
|
||||||
|
}
|
||||||
|
|
||||||
|
model: TableModel {
|
||||||
|
TableModelColumn { display: "select" }
|
||||||
|
TableModelColumn { display: "wafer" }
|
||||||
|
TableModelColumn { display: "date" }
|
||||||
|
TableModelColumn { display: "chamber" }
|
||||||
|
TableModelColumn { display: "notes" }
|
||||||
|
TableModelColumn { display: "fileName" }
|
||||||
|
TableModelColumn { display: "edit" }
|
||||||
|
TableModelColumn { display: "save" }
|
||||||
|
rows: root.normalizedRows
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property bool current
|
||||||
|
required property int row
|
||||||
|
required property int column
|
||||||
|
|
||||||
|
implicitHeight: 40
|
||||||
|
border.color: Theme.softBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
color: {
|
||||||
|
if (root.selectedRow === row) return Theme.sideActiveBackground;
|
||||||
|
if (root.tableModel[row]?.highlight) return Theme.subtleSectionBackground;
|
||||||
|
return row % 2 === 0 ? Theme.cardBackground : Theme.tone100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox (col 0)
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: column === 0
|
||||||
|
sourceComponent: CheckBox {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
checked: root.tableModel[row]?.selected ?? false
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (root.tableModel[row])
|
||||||
|
root.tableModel[row].selected = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit button (col 6)
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
active: column === 6
|
||||||
|
sourceComponent: Button {
|
||||||
|
hoverEnabled: true
|
||||||
|
text: "Edit…"
|
||||||
|
font.pixelSize: 12
|
||||||
|
onClicked: root.openMetadataEditor(row)
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: parent.down ? Theme.buttonNeutralPressed
|
||||||
|
: parent.hovered ? Theme.buttonNeutralHover
|
||||||
|
: Theme.buttonNeutralBackground
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: parent.text
|
||||||
|
color: Theme.buttonNeutralText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save button (col 7)
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
active: column === 7
|
||||||
|
sourceComponent: Button {
|
||||||
|
hoverEnabled: true
|
||||||
|
text: "Save"
|
||||||
|
font.pixelSize: 12
|
||||||
|
onClicked: {
|
||||||
|
const record = root.tableModel[row];
|
||||||
|
if (!record) return;
|
||||||
|
file_browser.saveMetadata(record.fileName ?? "", record.wafer ?? "", record.date ?? "", record.chamber ?? "", record.notes ?? "", record.selected ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: parent.down ? Theme.buttonNeutralPressed
|
||||||
|
: parent.hovered ? Theme.buttonNeutralHover
|
||||||
|
: Theme.buttonNeutralBackground
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: parent.text
|
||||||
|
color: Theme.buttonNeutralText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text columns: Wafer (1), Date (2), File (5)
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: column === 1 || column === 2 || column === 5
|
||||||
|
sourceComponent: Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: {
|
||||||
|
const record = root.tableModel[row] || {};
|
||||||
|
if (column === 1) return record.wafer ?? "";
|
||||||
|
if (column === 2) return record.date ?? "";
|
||||||
|
if (column === 5) return record.fileName ?? "";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: Theme.bodyColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text columns: Chamber (3), Notes (4)
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: column === 3 || column === 4
|
||||||
|
sourceComponent: Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: {
|
||||||
|
const record = root.tableModel[row] || {};
|
||||||
|
if (column === 3) return record.chamber ?? "";
|
||||||
|
return record.notes ?? "";
|
||||||
|
}
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: Theme.bodyColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: column !== 0 && column !== 6 && column !== 7
|
||||||
|
onClicked: {
|
||||||
|
root.selectedRow = row;
|
||||||
|
root.selectedPath = root.tableModel[row]?.fileName ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Bottom Bar =====
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: "OK"
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
onClicked: {
|
||||||
|
root.fileChosen(root.selectedPath);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: "Cancel"
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
onClicked: root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.minimumWidth: 120
|
||||||
|
clip: true
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: root.selectedPath
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: root.selectedPath ? Theme.fieldText : Theme.fieldPlaceholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Metadata Editor Dialog =====
|
||||||
|
Dialog {
|
||||||
|
id: metadataEditDialog
|
||||||
|
parent: Overlay.overlay
|
||||||
|
modal: true
|
||||||
|
title: qsTr("Edit File Information")
|
||||||
|
width: 520
|
||||||
|
height: 520
|
||||||
|
padding: 16
|
||||||
|
topPadding: 0
|
||||||
|
x: Math.round(((parent ? parent.width : width) - width) / 2)
|
||||||
|
y: Math.round(((parent ? parent.height : height) - height) / 2)
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusLg
|
||||||
|
color: Theme.panelBackground
|
||||||
|
border.color: Theme.outerFrameBorder
|
||||||
|
border.width: Theme.borderStrong
|
||||||
|
}
|
||||||
|
|
||||||
|
header: Rectangle {
|
||||||
|
width: metadataEditDialog.width
|
||||||
|
height: 48
|
||||||
|
radius: Theme.radiusLg
|
||||||
|
color: Theme.subtleSectionBackground
|
||||||
|
border.color: Theme.innerFrameBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: parent.radius
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
text: metadataEditDialog.title
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.headingColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.settingsGridSpacing
|
||||||
|
|
||||||
|
Label { text: qsTr("File"); font.bold: true; color: Theme.headingColor }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: filePathLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumHeight: 56
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
maximumLineCount: 3
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
Label { text: qsTr("Wafer"); color: Theme.headingColor }
|
||||||
|
DialogTextInput { id: waferField; Layout.fillWidth: true; Layout.preferredHeight: 36 }
|
||||||
|
|
||||||
|
Label { text: qsTr("Date"); color: Theme.headingColor }
|
||||||
|
DialogTextInput { id: dateField; Layout.fillWidth: true; Layout.preferredHeight: 36 }
|
||||||
|
|
||||||
|
Label { text: qsTr("Chamber"); color: Theme.headingColor }
|
||||||
|
DialogTextInput { id: chamberField; Layout.fillWidth: true; Layout.preferredHeight: 36 }
|
||||||
|
|
||||||
|
Label { text: qsTr("Notes"); color: Theme.headingColor }
|
||||||
|
DialogTextInput { id: notesField; Layout.fillWidth: true; Layout.preferredHeight: 36 }
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: selectedField
|
||||||
|
text: qsTr("Selected for processing")
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 8
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
onClicked: {
|
||||||
|
root.metadataEditRow = -1;
|
||||||
|
metadataEditDialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogActionButton {
|
||||||
|
text: qsTr("Save")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
onClicked: root.applyMetadataFromEditor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
import ISC
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// ===== CSV metadata dialog (must not live inside RowLayout: implicit size breaks the tab) =====
|
||||||
|
SelectFileDialog {
|
||||||
|
id: selectFileDialog
|
||||||
|
tableModel: file_browser.files
|
||||||
|
onFileChosen: function (path) {
|
||||||
|
root.handleCsvSelected(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditCsvMetadata() {
|
||||||
|
file_browser.refreshFiles();
|
||||||
|
selectFileDialog.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCsvSelected(filePath) {
|
||||||
|
console.log("Doing work with:", filePath);
|
||||||
|
// @todo: implement CSV metadata handling after file selection.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Settings Data Helpers =====
|
||||||
|
readonly property var masterFamilies: ["A", "B", "C", "D", "E", "F", "P", "X", "Z"]
|
||||||
|
|
||||||
|
function masterPath(family) {
|
||||||
|
const table = settingsModel.masters;
|
||||||
|
return table && table[family] ? table[family] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusColor() {
|
||||||
|
if (!settingsModel.isValid) {
|
||||||
|
return Theme.statusWarningColor;
|
||||||
|
}
|
||||||
|
return settingsModel.saveStatus.indexOf("error:") === 0 ? Theme.statusErrorColor : Theme.statusSuccessColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Reusable Settings Components =====
|
||||||
|
component SettingsGroupBox: GroupBox {
|
||||||
|
id: settingsGroup
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
y: settingsGroup.topPadding - settingsGroup.bottomPadding
|
||||||
|
width: settingsGroup.width
|
||||||
|
height: settingsGroup.height - settingsGroup.topPadding + settingsGroup.bottomPadding
|
||||||
|
radius: Theme.radiusMd
|
||||||
|
color: Theme.panelBackground
|
||||||
|
border.color: Theme.innerFrameBorder
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
}
|
||||||
|
|
||||||
|
label: Label {
|
||||||
|
text: settingsGroup.title
|
||||||
|
color: Theme.panelTitleText
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component SettingsTextInput: TextField {
|
||||||
|
id: textInput
|
||||||
|
color: Theme.fieldText
|
||||||
|
placeholderTextColor: Theme.fieldPlaceholder
|
||||||
|
selectedTextColor: Theme.fieldBackground
|
||||||
|
selectionColor: Theme.fieldText
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
border.width: textInput.activeFocus ? Theme.borderStrong : Theme.borderThin
|
||||||
|
border.color: textInput.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component SettingsActionButton: Button {
|
||||||
|
id: actionButton
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
color: actionButton.down ? Theme.buttonNeutralPressed : (actionButton.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground)
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
border.color: Theme.fieldBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: actionButton.text
|
||||||
|
color: Theme.buttonNeutralText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component SettingsToggle: CheckBox {
|
||||||
|
id: toggle
|
||||||
|
indicator: Rectangle {
|
||||||
|
implicitWidth: 20
|
||||||
|
implicitHeight: 20
|
||||||
|
x: toggle.leftPadding
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
color: toggle.checked ? Theme.primaryAccent : Theme.fieldBackground
|
||||||
|
border.width: Theme.borderThin
|
||||||
|
border.color: toggle.checked ? Theme.primaryAccent : Theme.fieldBorder
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 9
|
||||||
|
height: 9
|
||||||
|
radius: Theme.radiusXs
|
||||||
|
visible: toggle.checked
|
||||||
|
color: Theme.fieldBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: toggle.text
|
||||||
|
color: Theme.checkboxText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
leftPadding: toggle.indicator.width + toggle.spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== File Picker Dialog =====
|
||||||
|
FileDialog {
|
||||||
|
id: masterPicker
|
||||||
|
title: "Choose Master CSV"
|
||||||
|
nameFilters: ["CSV files (*.csv)"]
|
||||||
|
property string targetFamily: ""
|
||||||
|
onAccepted: settingsModel.setMaster(targetFamily, selectedFile.toLocalFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Settings Page Layout =====
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: settingsScroll
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.settingsOuterMargin
|
||||||
|
|
||||||
|
contentWidth: availableWidth
|
||||||
|
contentHeight: container.implicitHeight
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
parent: settingsScroll
|
||||||
|
anchors.top: settingsScroll.top
|
||||||
|
anchors.right: settingsScroll.right
|
||||||
|
anchors.bottom: settingsScroll.bottom
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
|
||||||
|
contentItem: Rectangle {
|
||||||
|
implicitWidth: 8
|
||||||
|
implicitHeight: 8
|
||||||
|
radius: Theme.radiusSm
|
||||||
|
color: Theme.trackBackground
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: 8
|
||||||
|
color: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: container
|
||||||
|
width: Math.min(settingsScroll.availableWidth, Theme.settingsPanelMaxWidth)
|
||||||
|
x: (settingsScroll.availableWidth - width) / 2
|
||||||
|
spacing: Theme.settingsSectionSpacing
|
||||||
|
|
||||||
|
// ===== Page Title =====
|
||||||
|
Label {
|
||||||
|
text: "Settings"
|
||||||
|
font.pixelSize: 30
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.headingColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Chamber Settings =====
|
||||||
|
SettingsGroupBox {
|
||||||
|
title: "Chamber"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.settingsRowSpacing
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Chamber ID"
|
||||||
|
color: Theme.headingColor
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsTextInput {
|
||||||
|
id: chamberField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: Theme.settingsFieldMinWidth
|
||||||
|
placeholderText: "Enter chamber ID"
|
||||||
|
text: settingsModel.chamberId
|
||||||
|
onEditingFinished: settingsModel.setChamberId(text)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: settingsModel
|
||||||
|
function onChamberIdChanged() {
|
||||||
|
if (!chamberField.activeFocus) {
|
||||||
|
chamberField.text = settingsModel.chamberId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsActionButton {
|
||||||
|
text: "Set"
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||||
|
onClicked: settingsModel.setChamberId(chamberField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Master CSV Mapping =====
|
||||||
|
SettingsGroupBox {
|
||||||
|
title: "Master Files"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.settingsGridSpacing
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.masterFamilies
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
property string family: modelData
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.settingsRowSpacing
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: family
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.headingColor
|
||||||
|
Layout.preferredWidth: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsTextInput {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
readOnly: true
|
||||||
|
text: root.masterPath(family)
|
||||||
|
placeholderText: "No master selected"
|
||||||
|
color: Theme.bodyColor
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsActionButton {
|
||||||
|
text: "Browse"
|
||||||
|
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||||
|
onClicked: {
|
||||||
|
masterPicker.targetFamily = family;
|
||||||
|
masterPicker.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsActionButton {
|
||||||
|
text: "Clear"
|
||||||
|
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||||
|
onClicked: settingsModel.clearMaster(family)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Wafer Behavior =====
|
||||||
|
SettingsGroupBox {
|
||||||
|
title: "Wafer Behavior"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.settingsGridSpacing
|
||||||
|
|
||||||
|
SettingsToggle {
|
||||||
|
text: "Reverse Z Wafer"
|
||||||
|
checked: settingsModel.reverseZWafer
|
||||||
|
onToggled: settingsModel.setReverseZWafer(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Appearance =====
|
||||||
|
SettingsGroupBox {
|
||||||
|
title: "Appearance"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.settingsRowSpacing
|
||||||
|
|
||||||
|
// Custom pill toggle
|
||||||
|
Rectangle {
|
||||||
|
id: toggleTrack
|
||||||
|
implicitWidth: 52
|
||||||
|
implicitHeight: 28
|
||||||
|
radius: height / 2
|
||||||
|
color: Theme.trackBackground
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: implicitWidth
|
||||||
|
Layout.preferredHeight: implicitHeight
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumb
|
||||||
|
Rectangle {
|
||||||
|
id: toggleThumb
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: height / 2
|
||||||
|
color: "white"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
x: Theme.isDarkMode ? parent.width - width - 3 : 3
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop shadow effect
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: null // swap for DropShadow if Qt.labs.effects is available
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Theme.isDarkMode = !Theme.isDarkMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label
|
||||||
|
Label {
|
||||||
|
text: Theme.isDarkMode ? "Dark" : "Light"
|
||||||
|
color: Theme.bodyColor
|
||||||
|
font.pixelSize: 13
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Adjustment Entry Point =====
|
||||||
|
RowLayout {
|
||||||
|
id: adjustmentRow
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Theme.settingsRowSpacing
|
||||||
|
|
||||||
|
SettingsActionButton {
|
||||||
|
text: "Edit CSV Metadata"
|
||||||
|
Layout.preferredWidth: Theme.settingsActionWidth
|
||||||
|
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||||
|
onClicked: root.handleEditCsvMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Scroll hint overlay (fades out when scrolled) =====
|
||||||
|
Rectangle {
|
||||||
|
id: scrollHint
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: 48
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop {
|
||||||
|
position: 0.0
|
||||||
|
color: Theme.isDarkMode ? 'rgba(30,30,35,1)' : 'rgba(255,255,255,1)'
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1.0
|
||||||
|
color: Qt.transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "↓ Scroll for more"
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.bodyColor
|
||||||
|
opacity: (settingsScroll.ScrollBar.vertical.position <= 0 && settingsScroll.contentHeight > settingsScroll.height) ? (scrollHintTimer.running ? 1 : 0) : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 400 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Auto-hide scroll hint after first interaction =====
|
||||||
|
Timer {
|
||||||
|
id: scrollHintTimer
|
||||||
|
running: true
|
||||||
|
repeat: false
|
||||||
|
onTriggered: scrollHint.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -4,4 +4,4 @@ name = "PySide QtQuick Project"
|
|||||||
|
|
||||||
# ===== PySide Build Inputs =====
|
# ===== PySide Build Inputs =====
|
||||||
[tool.pyside6-project]
|
[tool.pyside6-project]
|
||||||
files = ["ISC/HomePage.qml", "ISC/Main.qml", "ISC/Theme.qml", "ISC/Tabs/SettingsTab.qml", "ISC/Tabs/qmldir", "ISC/qmldir", "main.py", "backend/local_settings.py", "backend/local_settings_model.py"]
|
files = ["ISC/HomePage.qml", "ISC/Main.qml", "ISC/Tabs/SelectFileDialog.qml", "ISC/Tabs/SettingsTab.qml", "ISC/Tabs/qmldir", "ISC/Theme.qml", "ISC/qmldir", "backend/file_browser.py", "backend/local_settings.py", "backend/local_settings_model.py", "main.py"]
|
||||||
|
|||||||
Reference in New Issue
Block a user