feat: implement C#-compatible AES-128-CBC license file parsing and integrate license management UI into AboutDialog

This commit is contained in:
jack
2026-07-07 11:15:06 -07:00
parent e2d05d2c33
commit 7df7fd4c6f
8 changed files with 803 additions and 82 deletions
+95 -36
View File
@@ -1,25 +1,20 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import ISC
// TODO P3.1 + P3.2: license grid, "Load License" picker, version binding
// THINKING: C# parity requires the dgvLicense grid (Wafer SN, Mfg Date,
// License Level, License Date) fed from AES-128-CBC .bin files under
// <app_data>/licenses/ — CryptoHelper (backend/crypto/crypto_helper.py)
// already has the primitives; the missing piece is the license file model
// (P3.1 backend). "Version 0.1.0" below is hardcoded and will drift from
// pyproject.toml — expose importlib.metadata.version("pygui") via a context
// property instead (P3.2). See docs/pending/alpha-release-polish-plan.md §3.
Popup {
id: root
modal: true
dim: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
width: 420
height: 340
width: 480
height: 440
anchors.centerIn: Overlay.overlay
onOpened: licenseModel.refresh()
background: Rectangle {
radius: Theme.radiusMd
color: Theme.cardBackground
@@ -27,6 +22,13 @@ Popup {
border.width: 1
}
FileDialog {
id: licenseFileDialog
title: "Load License"
nameFilters: ["License files (*.bin)"]
onAccepted: loadFailedLabel.visible = !licenseModel.loadLicenseFile(selectedFile.toString())
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 24
@@ -51,7 +53,7 @@ Popup {
}
Label {
text: "Version 0.1.0"
text: "Version " + appVersion
font.pixelSize: Theme.fontSm
color: Theme.bodyColor
}
@@ -75,7 +77,8 @@ Popup {
// ── License grid ──
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 100
Layout.fillHeight: true
Layout.minimumHeight: 120
radius: Theme.radiusSm
color: Theme.panelBackground
border.color: Theme.cardBorder
@@ -86,11 +89,24 @@ Popup {
anchors.margins: 10
spacing: 2
Label {
text: "LICENSE"
color: Theme.panelTitleText
font.pixelSize: Theme.fontXs
font.letterSpacing: 0.5
RowLayout {
Layout.fillWidth: true
Label {
text: "LICENSE"
color: Theme.panelTitleText
font.pixelSize: Theme.fontXs
font.letterSpacing: 0.5
Layout.fillWidth: true
}
Label {
id: loadFailedLabel
visible: false
text: "Invalid license file"
color: Theme.statusWarningColor
font.pixelSize: Theme.fontXs
}
}
// Grid header
@@ -98,8 +114,9 @@ Popup {
Layout.fillWidth: true
spacing: 4
Label { text: "Wafer SN"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.preferredWidth: 100 }
Label { text: "Mfg Date"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.preferredWidth: 80 }
Label { text: "Level"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.preferredWidth: 60 }
Label { text: "Mfg Date"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.preferredWidth: 90 }
Label { text: "Level"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.preferredWidth: 50 }
Label { text: "License Date"; font.pixelSize: Theme.fontXs; font.bold: true; color: Theme.subheadingColor; Layout.fillWidth: true }
}
Rectangle {
@@ -108,7 +125,24 @@ Popup {
color: Theme.softBorder
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
visible: licenseModel.licenses.length > 0
model: licenseModel.licenses
delegate: RowLayout {
width: ListView.view.width
spacing: 4
Label { text: modelData.serial; font.pixelSize: Theme.fontSm; color: Theme.bodyColor; Layout.preferredWidth: 100 }
Label { text: modelData.mfgDate; font.pixelSize: Theme.fontSm; color: Theme.bodyColor; Layout.preferredWidth: 90 }
Label { text: modelData.level; font.pixelSize: Theme.fontSm; color: Theme.bodyColor; Layout.preferredWidth: 50 }
Label { text: modelData.licDate; font.pixelSize: Theme.fontSm; color: Theme.bodyColor; Layout.fillWidth: true }
}
}
Label {
visible: licenseModel.licenses.length === 0
text: "No license loaded"
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
@@ -120,25 +154,50 @@ Popup {
}
}
// ── Close ──
Button {
text: "Close"
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: 100
Layout.preferredHeight: 32
onClicked: root.close()
// ── Buttons ──
RowLayout {
Layout.fillWidth: true
background: Rectangle {
radius: Theme.radiusSm
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
border.color: Theme.fieldBorder
border.width: 1
Button {
text: "Load License"
Layout.preferredWidth: 120
Layout.preferredHeight: 32
onClicked: licenseFileDialog.open()
background: Rectangle {
radius: Theme.radiusSm
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
border.color: Theme.fieldBorder
border.width: 1
}
contentItem: Label {
text: parent.text
color: Theme.buttonNeutralText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
contentItem: Label {
text: parent.text
color: Theme.buttonNeutralText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Item { Layout.fillWidth: true }
Button {
text: "Close"
Layout.preferredWidth: 100
Layout.preferredHeight: 32
onClicked: root.close()
background: Rectangle {
radius: Theme.radiusSm
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
border.color: Theme.fieldBorder
border.width: 1
}
contentItem: Label {
text: parent.text
color: Theme.buttonNeutralText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
}