import QtQuick import QtQuick.Controls as Controls import QtQuick.Layouts import QtQuick.Dialogs import org.kde.kirigami as Kirigami Kirigami.ApplicationWindow { id: root visible: true width: 760 height: 820 minimumWidth: 600 minimumHeight: 580 title: qsTr("ALT Image Writer") readonly property string supportedBoardsText: @SUPPORTED_BOARDS@ readonly property string availableDrivesText: @AVAILABLE_DRIVES@ property bool sourceIsImage: false property bool outputIsFile: false property string sourcePath: "" property string outputPath: "" property string selectedBoard: "" property string selectedPlatform: "" property var driveNames: availableDrivesText.length ? availableDrivesText.split("\n") : [] function localPath(url) { var value = url.toString() return value.startsWith("file://") ? decodeURIComponent(value.substring(7)) : value } function fileName(path) { var parts = path.split("/") return parts[parts.length - 1] } function ready() { return sourcePath.length > 0 && selectedBoard.length > 0 && (outputIsFile ? outputPath.length > 0 : driveBox.currentIndex >= 0 && driveNames.length > 0) } globalDrawer: Kirigami.GlobalDrawer { isMenu: true actions: [ Kirigami.Action { text: qsTr("About ALT Image Writer") icon.name: "help-about" onTriggered: aboutDialog.open() } ] } pageStack.initialPage: Kirigami.ScrollablePage { title: qsTr("Create Installation Media") actions: [ Kirigami.Action { text: qsTr("About") icon.name: "help-about" displayHint: Kirigami.DisplayHint.IconOnly onTriggered: aboutDialog.open() } ] ColumnLayout { width: Math.min(parent.width, Kirigami.Units.gridUnit * 42) anchors.horizontalCenter: parent.horizontalCenter spacing: Kirigami.Units.largeSpacing Controls.Label { text: qsTr("Prepare an ALT Linux root filesystem or disk image for your board.") color: Kirigami.Theme.disabledTextColor horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap Layout.fillWidth: true } Kirigami.AbstractCard { Layout.fillWidth: true contentItem: ColumnLayout { spacing: Kirigami.Units.smallSpacing Kirigami.Heading { text: qsTr("Installation Source"); level: 3 } Controls.Label { text: qsTr("Choose a root filesystem archive or a ready-made disk image.") color: Kirigami.Theme.disabledTextColor wrapMode: Text.WordWrap Layout.fillWidth: true } Kirigami.FormLayout { Layout.fillWidth: true RowLayout { Kirigami.FormData.label: qsTr("Source type:") Controls.RadioButton { text: qsTr("Root filesystem") checked: true onCheckedChanged: if (checked) sourceIsImage = false } Controls.RadioButton { text: qsTr("Disk image") onCheckedChanged: if (checked) { sourceIsImage = true driveOutput.checked = true } } } RowLayout { Kirigami.FormData.label: qsTr("Source:") Controls.Label { text: sourcePath.length ? fileName(sourcePath) : qsTr("No source selected") elide: Text.ElideMiddle Layout.fillWidth: true } Controls.Button { text: qsTr("Choose…") icon.name: "document-open" onClicked: sourceDialog.open() } } } Controls.Label { text: sourcePath.length ? sourcePath : sourceIsImage ? qsTr("Supported: .img, .img.xz") : qsTr("Supported: .tar, .tar.gz, .tar.xz") color: Kirigami.Theme.disabledTextColor elide: Text.ElideMiddle Layout.fillWidth: true } } } Kirigami.AbstractCard { Layout.fillWidth: true contentItem: ColumnLayout { Kirigami.Heading { text: qsTr("Target Hardware"); level: 3 } Controls.Label { text: qsTr("Boards are grouped by platform, SoC family, and architecture.") color: Kirigami.Theme.disabledTextColor wrapMode: Text.WordWrap Layout.fillWidth: true } RowLayout { Layout.fillWidth: true Kirigami.Icon { source: "computer-symbolic" implicitWidth: Kirigami.Units.iconSizes.medium implicitHeight: implicitWidth } ColumnLayout { spacing: 0 Layout.fillWidth: true Controls.Label { text: selectedBoard.length ? selectedBoard : qsTr("No board selected") font.bold: selectedBoard.length > 0 } Controls.Label { text: selectedBoard.length ? selectedPlatform : qsTr("Choose a supported target board") color: Kirigami.Theme.disabledTextColor } } Controls.Button { text: qsTr("Select Board…") onClicked: boardDialog.open() } } } } Kirigami.AbstractCard { Layout.fillWidth: true contentItem: ColumnLayout { Kirigami.Heading { text: qsTr("Destination"); level: 3 } Controls.Label { text: qsTr("Write to removable media or create an image file.") color: Kirigami.Theme.disabledTextColor } Kirigami.FormLayout { Layout.fillWidth: true RowLayout { Kirigami.FormData.label: qsTr("Output type:") Controls.RadioButton { id: driveOutput text: qsTr("Drive") checked: true onCheckedChanged: if (checked) outputIsFile = false } Controls.RadioButton { text: qsTr("Image file") enabled: !sourceIsImage onCheckedChanged: if (checked) outputIsFile = true } } Controls.ComboBox { id: driveBox visible: !outputIsFile Kirigami.FormData.label: qsTr("Storage device:") Layout.fillWidth: true model: driveNames displayText: currentIndex < 0 || driveNames.length === 0 ? qsTr("No removable drives detected") : currentText enabled: driveNames.length > 0 } RowLayout { visible: outputIsFile Kirigami.FormData.label: qsTr("Image file:") Controls.Label { text: outputPath.length ? outputPath : qsTr("Choose where to save the image") elide: Text.ElideMiddle Layout.fillWidth: true } Controls.Button { text: qsTr("Choose…") icon.name: "document-save" onClicked: outputDialog.open() } } } } } Kirigami.AbstractCard { Layout.fillWidth: true contentItem: ColumnLayout { Kirigami.Heading { text: qsTr("Options"); level: 3 } Kirigami.FormLayout { Layout.fillWidth: true Controls.ComboBox { Kirigami.FormData.label: qsTr("Root filesystem:") model: ["ext4", "f2fs"] } Controls.CheckBox { Kirigami.FormData.label: qsTr("Resize root partition:") text: qsTr("Grow to fill the destination") checked: true } Controls.CheckBox { Kirigami.FormData.label: qsTr("Boot partition:") text: qsTr("Create a separate 512 MiB partition") } Controls.CheckBox { Kirigami.FormData.label: qsTr("Encryption:") text: qsTr("Encrypt root partition with LUKS") } Controls.CheckBox { Kirigami.FormData.label: qsTr("Serial console:") text: qsTr("Add to boot arguments") } Controls.ComboBox { Kirigami.FormData.label: qsTr("EFI system partition:") model: [qsTr("None"), qsTr("EFI (GPT)"), qsTr("EFI (MBR)")] } Controls.ComboBox { Kirigami.FormData.label: qsTr("First-boot VNC:") model: [qsTr("Use image default"), qsTr("Enabled"), qsTr("Disabled")] } } } } Kirigami.InlineMessage { id: previewMessage Layout.fillWidth: true type: Kirigami.MessageType.Information text: qsTr("Flashing is not implemented in this interface preview.") showCloseButton: true } } } footer: Controls.ToolBar { contentItem: RowLayout { Controls.Label { text: qsTr("Nothing will be written until you confirm.") color: Kirigami.Theme.disabledTextColor Layout.leftMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true } Controls.Button { text: qsTr("Review and Write") icon.name: "media-flash-sd-mmc" highlighted: true enabled: root.ready() Layout.rightMargin: Kirigami.Units.largeSpacing onClicked: reviewDialog.open() } } } Controls.Dialog { id: boardDialog parent: Controls.Overlay.overlay anchors.centerIn: parent width: Math.min(root.width - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 34) height: Math.min(root.height - Kirigami.Units.gridUnit * 4, Kirigami.Units.gridUnit * 34) modal: true title: qsTr("Select Target Board") standardButtons: Controls.Dialog.Cancel ListModel { id: allBoards } ListModel { id: filteredBoards } function populate() { if (allBoards.count > 0) return var platform = "Other" var lines = supportedBoardsText.split("\n") for (var i = 0; i < lines.length; ++i) { var line = lines[i].trim() if (!line.length) continue if (line.endsWith(":")) { platform = line.substring(0, line.length - 1) } else { var names = line.split(/\s+/) for (var j = 0; j < names.length; ++j) allBoards.append({ boardName: names[j], platformName: platform }) } } filter("") } function filter(query) { filteredBoards.clear() var needle = query.toLowerCase() for (var i = 0; i < allBoards.count; ++i) { var item = allBoards.get(i) if (!needle.length || item.boardName.toLowerCase().includes(needle) || item.platformName.toLowerCase().includes(needle)) filteredBoards.append({ boardName: item.boardName, platformName: item.platformName }) } } onOpened: { populate() searchField.forceActiveFocus() } contentItem: ColumnLayout { Controls.TextField { id: searchField Layout.fillWidth: true placeholderText: qsTr("Search boards or platforms…") onTextChanged: boardDialog.filter(text) } Controls.Label { text: qsTr("%1 matching boards").arg(filteredBoards.count) color: Kirigami.Theme.disabledTextColor } ListView { id: boardList Layout.fillWidth: true Layout.fillHeight: true clip: true model: filteredBoards section.property: "platformName" section.criteria: ViewSection.FullString section.delegate: Kirigami.ListSectionHeader { text: section } delegate: Controls.ItemDelegate { required property string boardName required property string platformName width: boardList.width text: boardName icon.name: "computer-symbolic" onClicked: { selectedBoard = boardName selectedPlatform = platformName boardDialog.close() } } Controls.ScrollBar.vertical: Controls.ScrollBar {} } } } Controls.Dialog { id: reviewDialog parent: Controls.Overlay.overlay anchors.centerIn: parent modal: true title: qsTr("Ready to write?") standardButtons: Controls.Dialog.Cancel | Controls.Dialog.Ok onAccepted: previewMessage.visible = true contentItem: Kirigami.FormLayout { Controls.Label { Kirigami.FormData.label: qsTr("Board:"); text: selectedBoard } Controls.Label { Kirigami.FormData.label: qsTr("Platform:"); text: selectedPlatform } Controls.Label { Kirigami.FormData.label: qsTr("Destination:") text: outputIsFile ? outputPath : driveBox.currentText elide: Text.ElideMiddle } Controls.Label { text: qsTr("This is an interface preview. No data will be written.") color: Kirigami.Theme.disabledTextColor wrapMode: Text.WordWrap } } } Controls.Dialog { id: aboutDialog parent: Controls.Overlay.overlay anchors.centerIn: parent modal: true title: qsTr("About ALT Image Writer") standardButtons: Controls.Dialog.Ok contentItem: ColumnLayout { Kirigami.Icon { source: "media-flash-sd-mmc" implicitWidth: Kirigami.Units.iconSizes.huge implicitHeight: implicitWidth Layout.alignment: Qt.AlignHCenter } Kirigami.Heading { text: qsTr("ALT Image Writer"); level: 2; Layout.alignment: Qt.AlignHCenter } Controls.Label { text: qsTr("Version 0.1.0"); Layout.alignment: Qt.AlignHCenter } Controls.Label { text: qsTr("Prepare ALT Linux installation media for supported single-board computers.") wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter Layout.preferredWidth: Kirigami.Units.gridUnit * 22 } } } FileDialog { id: sourceDialog title: qsTr("Choose Installation Source") fileMode: FileDialog.OpenFile nameFilters: sourceIsImage ? [qsTr("Disk images (*.img *.img.xz)"), qsTr("All files (*)")] : [qsTr("Root filesystem archives (*.tar *.tar.gz *.tar.xz)"), qsTr("All files (*)")] onAccepted: sourcePath = localPath(selectedFile) } FileDialog { id: outputDialog title: qsTr("Create Disk Image") fileMode: FileDialog.SaveFile defaultSuffix: "img" nameFilters: [qsTr("Raw disk images (*.img)"), qsTr("All files (*)")] onAccepted: outputPath = localPath(selectedFile) } }