Enhance UI and add file selection functionality

- Updated  to include new files for the project.
- Modified  to improve layout and navigation, including new properties for tab and action management.
- Introduced  for file selection with metadata editing capabilities.
- Created  for managing application settings and integrating the file selection dialog.
This commit is contained in:
Jack.Le
2026-04-27 13:56:36 -07:00
parent e43bf258e3
commit 9f8c6e1a4c
4 changed files with 1057 additions and 15 deletions
+553
View File
@@ -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()
}
}
}
}
}