import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import Qt.labs.platform 1.1 as Platform ApplicationWindow { id: window visible: true width: 760 height: 820 minimumWidth: 620 minimumHeight: 620 title: qsTr("ALT Image Writer — Qt") 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.indexOf("file://") === 0 ? 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) } header: ToolBar { RowLayout { anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 8 Label { text: qsTr("ALT Image Writer") font.pixelSize: 18 font.bold: true Layout.fillWidth: true } ToolButton { text: "ⓘ" font.pixelSize: 18 Accessible.name: qsTr("About") onClicked: aboutDialog.open() } } } ScrollView { id: scroll anchors.fill: parent contentWidth: availableWidth clip: true ColumnLayout { width: Math.min(scroll.availableWidth - 40, 700) x: Math.max(20, (scroll.availableWidth - width) / 2) spacing: 16 Item { Layout.preferredHeight: 8 } Label { text: qsTr("Create Installation Media") font.pixelSize: 26 font.bold: true Layout.alignment: Qt.AlignHCenter } Label { text: qsTr("Prepare an ALT Linux root filesystem or disk image for your board.") color: palette.mid wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true } Frame { Layout.fillWidth: true ColumnLayout { anchors.fill: parent spacing: 12 Label { text: qsTr("Installation Source"); font.bold: true; font.pixelSize: 17 } Label { text: qsTr("Choose a root filesystem archive or a ready-made disk image."); color: palette.mid } ButtonGroup { id: sourceGroup } RowLayout { Label { text: qsTr("Source type"); Layout.fillWidth: true } RadioButton { text: qsTr("Root filesystem") checked: true ButtonGroup.group: sourceGroup onCheckedChanged: if (checked) sourceIsImage = false } RadioButton { text: qsTr("Disk image") ButtonGroup.group: sourceGroup onCheckedChanged: if (checked) { sourceIsImage = true outputIsFile = false driveOutput.checked = true } } } RowLayout { spacing: 12 Label { text: sourcePath.length ? fileName(sourcePath) : qsTr("No source selected") font.bold: sourcePath.length > 0 elide: Text.ElideMiddle Layout.fillWidth: true } Button { text: qsTr("Choose…"); onClicked: sourceDialog.open() } } Label { text: sourcePath.length ? sourcePath : sourceIsImage ? qsTr("Supported: .img, .img.xz") : qsTr("Supported: .tar, .tar.gz, .tar.xz") color: palette.mid elide: Text.ElideMiddle Layout.fillWidth: true } } } Frame { Layout.fillWidth: true ColumnLayout { anchors.fill: parent spacing: 10 Label { text: qsTr("Target Hardware"); font.bold: true; font.pixelSize: 17 } Label { text: qsTr("Boards are grouped by platform, SoC family, and architecture."); color: palette.mid } RowLayout { ColumnLayout { Layout.fillWidth: true Label { text: selectedBoard.length ? selectedBoard : qsTr("No board selected") font.bold: selectedBoard.length > 0 } Label { text: selectedBoard.length ? selectedPlatform : qsTr("Choose from the supported board list") color: palette.mid } } Button { text: qsTr("Select Board…"); onClicked: boardDialog.open() } } } } Frame { Layout.fillWidth: true ColumnLayout { anchors.fill: parent spacing: 12 Label { text: qsTr("Destination"); font.bold: true; font.pixelSize: 17 } Label { text: qsTr("Write to removable media or create an image file."); color: palette.mid } ButtonGroup { id: outputGroup } RowLayout { Label { text: qsTr("Output type"); Layout.fillWidth: true } RadioButton { id: driveOutput text: qsTr("Drive") checked: true ButtonGroup.group: outputGroup onCheckedChanged: if (checked) outputIsFile = false } RadioButton { text: qsTr("Image file") enabled: !sourceIsImage ButtonGroup.group: outputGroup onCheckedChanged: if (checked) outputIsFile = true } } RowLayout { visible: !outputIsFile Label { text: qsTr("Storage device"); Layout.preferredWidth: 150 } ComboBox { id: driveBox Layout.fillWidth: true model: driveNames displayText: currentIndex < 0 ? qsTr("No removable drives detected") : currentText } } RowLayout { visible: outputIsFile Label { text: outputPath.length ? outputPath : qsTr("Choose where to save the image") elide: Text.ElideMiddle Layout.fillWidth: true } Button { text: qsTr("Choose…"); onClicked: outputDialog.open() } } } } Frame { Layout.fillWidth: true ColumnLayout { anchors.fill: parent spacing: 8 Label { text: qsTr("Options"); font.bold: true; font.pixelSize: 17 } RowLayout { Label { text: qsTr("Root filesystem"); Layout.fillWidth: true } ComboBox { model: ["ext4", "f2fs"] } } CheckBox { text: qsTr("Resize root partition to fill the destination"); checked: true } CheckBox { text: qsTr("Create a separate 512 MiB boot partition") } CheckBox { text: qsTr("Encrypt the root partition with LUKS") } CheckBox { text: qsTr("Add the system serial console") } RowLayout { Label { text: qsTr("EFI system partition"); Layout.fillWidth: true } ComboBox { model: [qsTr("None"), qsTr("EFI (GPT)"), qsTr("EFI (MBR)")] } } RowLayout { Label { text: qsTr("First-boot VNC"); Layout.fillWidth: true } ComboBox { model: [qsTr("Use image default"), qsTr("Enabled"), qsTr("Disabled")] } } } } Item { Layout.preferredHeight: 80 } } } footer: ToolBar { RowLayout { anchors.fill: parent anchors.margins: 10 Label { text: qsTr("Nothing will be written until you confirm."); color: palette.mid; Layout.fillWidth: true } Button { text: qsTr("Review and Write") highlighted: true enabled: ready() onClicked: reviewDialog.open() } } } Dialog { id: boardDialog parent: Overlay.overlay anchors.centerIn: parent width: Math.min(window.width - 48, 620) height: Math.min(window.height - 80, 650) modal: true title: qsTr("Select Target Board") standardButtons: 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.charAt(line.length - 1) === ":") { 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().indexOf(needle) >= 0 || item.platformName.toLowerCase().indexOf(needle) >= 0) filteredBoards.append({ boardName: item.boardName, platformName: item.platformName }) } } onOpened: { populate(); searchField.forceActiveFocus() } ColumnLayout { anchors.fill: parent TextField { id: searchField Layout.fillWidth: true placeholderText: qsTr("Search boards or platforms…") selectByMouse: true onTextChanged: boardDialog.filter(text) } Label { text: qsTr("%1 matching boards").arg(filteredBoards.count) color: palette.mid } ListView { id: boardList Layout.fillWidth: true Layout.fillHeight: true clip: true model: filteredBoards spacing: 2 section.property: "platformName" section.criteria: ViewSection.FullString section.delegate: Rectangle { width: boardList.width height: 42 color: window.palette.alternateBase Label { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: section font.bold: true color: window.palette.highlight } } delegate: ItemDelegate { width: boardList.width text: boardName onClicked: { selectedBoard = boardName selectedPlatform = platformName boardDialog.close() } } ScrollBar.vertical: ScrollBar {} } } } Dialog { id: reviewDialog parent: Overlay.overlay anchors.centerIn: parent modal: true title: qsTr("Ready to write?") standardButtons: Dialog.Cancel | Dialog.Ok onAccepted: previewNotice.open() ColumnLayout { Label { text: qsTr("Board"); font.bold: true } Label { text: selectedBoard + " — " + selectedPlatform } Label { text: qsTr("Destination"); font.bold: true } Label { text: outputIsFile ? outputPath : driveBox.currentText; elide: Text.ElideMiddle; Layout.maximumWidth: 450 } Label { text: qsTr("This is an interface preview. No data will be written."); color: palette.mid } } } Dialog { id: aboutDialog parent: Overlay.overlay anchors.centerIn: parent modal: true title: qsTr("About ALT Image Writer") standardButtons: Dialog.Ok Label { text: qsTr("ALT Image Writer — Qt\nVersion 0.1.0\n\nPrepare ALT Linux installation media for supported SBCs.") horizontalAlignment: Text.AlignHCenter } } Popup { id: previewNotice parent: Overlay.overlay x: (parent.width - width) / 2 y: parent.height - height - 24 padding: 16 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside Label { text: qsTr("Flashing is not implemented in this preview") } Timer { interval: 3500; running: previewNotice.visible; onTriggered: previewNotice.close() } } Platform.FileDialog { id: sourceDialog title: qsTr("Choose Installation Source") fileMode: Platform.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(file) } Platform.FileDialog { id: outputDialog title: qsTr("Create Disk Image") fileMode: Platform.FileDialog.SaveFile defaultSuffix: "img" nameFilters: [qsTr("Raw disk images (*.img)"), qsTr("All files (*)")] onAccepted: outputPath = localPath(file) } }