/* * SPDX-License-Identifier: Apache-2.0 * Copyright (C) 2020 Raspberry Pi Ltd */ import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.0 import QtQuick.Controls.Material 2.2 import "qmlcomponents" ApplicationWindow { id: window visible: true width: imageWriter.isEmbeddedMode() ? -1 : 680 height: imageWriter.isEmbeddedMode() ? -1 : 450 minimumWidth: imageWriter.isEmbeddedMode() ? -1 : 680 minimumHeight: imageWriter.isEmbeddedMode() ? -1 : 420 title: qsTr("Raspberry Pi Imager v%1").arg(imageWriter.constantVersion()) FontLoader {id: roboto; source: "fonts/Roboto-Regular.ttf"} FontLoader {id: robotoLight; source: "fonts/Roboto-Light.ttf"} FontLoader {id: robotoBold; source: "fonts/Roboto-Bold.ttf"} /** hw device list storage * * To allow us to filter the OS list, we maintain an application-wide record of the selected device * tags. */ property string hwTags /** 0: Exclusive, must match explicit device names only, no untagged 1: Exclusive by prefix, must match the device name as a prefix, no untagged 2: Inclusive, match explicit device names and untagged 3: Inclusive by prefix, match explicit device names and untagged */ property int hwTagMatchingType onClosing: { if (progressBar.visible) { close.accepted = false quitpopup.openPopup() } } Shortcut { sequence: StandardKey.Quit context: Qt.ApplicationShortcut onActivated: { if (!progressBar.visible) { Qt.quit() } } } Shortcut { sequences: ["Shift+Ctrl+X", "Shift+Meta+X"] context: Qt.ApplicationShortcut onActivated: { optionspopup.openPopup() } } ColumnLayout { id: bg spacing: 0 Rectangle { implicitHeight: window.height/3 Image { id: image //Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter fillMode: Image.PreserveAspectFit source: "icons/logo_sxs_imager.png" width: window.width height: window.height/3 } } Rectangle { color: "#c31c4a" implicitWidth: window.width implicitHeight: (window.height/3) * 2 GridLayout { id: gridLayout rowSpacing: 15 anchors.fill: parent anchors.topMargin: 25 anchors.rightMargin: 50 anchors.leftMargin: 50 rows: 5 columns: 3 columnSpacing: 15 ColumnLayout { id: columnLayout0 spacing: 0 Layout.row: 0 Layout.column: 0 Layout.fillWidth: true Text { id: text0 color: "#ffffff" text: qsTr("Select your device") Layout.fillWidth: true Layout.preferredHeight: 17 Layout.preferredWidth: 100 font.pixelSize: 12 font.family: robotoBold.name font.bold: true horizontalAlignment: Text.AlignHCenter } ImButton { id: hwbutton text: qsTr("CHOOSE DEVICE") spacing: 0 padding: 0 bottomPadding: 0 topPadding: 0 Layout.minimumHeight: 40 Layout.fillWidth: true onClicked: { hwpopup.open() hwlistview.currentItem.forceActiveFocus() } Accessible.ignored: ospopup.visible || dstpopup.visible || hwpopup.visible Accessible.description: qsTr("Select this button to choose your target Raspberry Pi") } } ColumnLayout { id: columnLayout1 spacing: 0 Layout.row: 0 Layout.column: 1 Layout.fillWidth: true Text { id: text1 color: "#ffffff" text: qsTr("Operating System") Layout.fillWidth: true Layout.preferredHeight: 17 font.pixelSize: 12 font.family: robotoBold.name font.bold: true horizontalAlignment: Text.AlignHCenter } ImButton { id: osbutton text: imageWriter.srcFileName() === "" ? qsTr("CHOOSE OS") : imageWriter.srcFileName() spacing: 0 padding: 0 bottomPadding: 0 topPadding: 0 Layout.minimumHeight: 40 Layout.fillWidth: true onClicked: { ospopup.open() osswipeview.currentItem.forceActiveFocus() } Accessible.ignored: ospopup.visible || dstpopup.visible || hwpopup.visible Accessible.description: qsTr("Select this button to change the operating system") } } ColumnLayout { id: columnLayout2 spacing: 0 Layout.row: 0 Layout.column: 2 Layout.fillWidth: true Text { id: text2 color: "#ffffff" text: qsTr("Storage") Layout.fillWidth: true Layout.preferredHeight: 17 font.pixelSize: 12 font.family: robotoBold.name font.bold: true horizontalAlignment: Text.AlignHCenter } ImButton { id: dstbutton text: qsTr("CHOOSE STORAGE") spacing: 0 padding: 0 bottomPadding: 0 topPadding: 0 Layout.minimumHeight: 40 Layout.preferredWidth: 200 Layout.fillWidth: true onClicked: { imageWriter.startDriveListPolling() dstpopup.open() dstlist.forceActiveFocus() } Accessible.ignored: ospopup.visible || dstpopup.visible || hwpopup.visible Accessible.description: qsTr("Select this button to change the destination storage device") } } ColumnLayout { id: columnLayoutProgress spacing: 0 Layout.row: 1 Layout.column: 0 Layout.columnSpan: 2 Text { id: progressText font.pointSize: 10 color: "white" font.family: robotoBold.name font.bold: true visible: false horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.bottomMargin: 25 padding: 5 } ProgressBar { Layout.bottomMargin: 25 padding: 5 id: progressBar Layout.fillWidth: true visible: false Material.background: "#d15d7d" } } ColumnLayout { id: columnLayout3 Layout.row: 1 Layout.column: 2 Layout.alignment: Qt.AlignRight | Qt.AlignVCenter spacing: 0 ImButton { Layout.bottomMargin: 25 Layout.minimumHeight: 40 Layout.preferredWidth: 200 padding: 5 id: cancelwritebutton text: qsTr("CANCEL WRITE") onClicked: { enabled = false progressText.text = qsTr("Cancelling...") imageWriter.cancelWrite() } Layout.alignment: Qt.AlignRight visible: false } ImButton { Layout.bottomMargin: 25 Layout.minimumHeight: 40 Layout.preferredWidth: 200 padding: 5 id: cancelverifybutton text: qsTr("CANCEL VERIFY") onClicked: { enabled = false progressText.text = qsTr("Finalizing...") imageWriter.setVerifyEnabled(false) } Layout.alignment: Qt.AlignRight visible: false } ImButton { Layout.bottomMargin: 25 Layout.minimumHeight: 40 Layout.preferredWidth: 200 Layout.alignment: Qt.AlignRight padding: 5 id: customizebutton onClicked: { optionspopup.openPopup() } visible: imageWriter.imageSupportsCustomization() Accessible.description: qsTr("Select this button to access advanced settings") contentItem: Image { source: "icons/ic_cog_red.svg" fillMode: Image.PreserveAspectFit } } ImButton { id: writebutton text: qsTr("WRITE") Layout.bottomMargin: 25 Layout.minimumHeight: 40 Layout.preferredWidth: 200 Layout.alignment: Qt.AlignRight Accessible.ignored: ospopup.visible || dstpopup.visible || hwpopup.visible Accessible.description: qsTr("Select this button to start writing the image") enabled: false onClicked: { if (!imageWriter.readyToWrite()) { return } if (!optionspopup.initialized && imageWriter.imageSupportsCustomization() && imageWriter.hasSavedCustomizationSettings()) { usesavedsettingspopup.openPopup() } else { confirmwritepopup.askForConfirmation() } } } } Text { Layout.columnSpan: 3 color: "#ffffff" font.pixelSize: 18 font.family: roboto.name visible: imageWriter.isEmbeddedMode() && imageWriter.customRepo() text: qsTr("Using custom repository: %1").arg(imageWriter.constantOsListUrl()) } Text { Layout.columnSpan: 3 color: "#ffffff" font.pixelSize: 18 font.family: roboto.name visible: !imageWriter.hasMouse() text: qsTr("Keyboard navigation: navigate to next button press button/select item go up/down in lists") } Rectangle { id: langbarRect Layout.columnSpan: 3 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: 5 visible: imageWriter.isEmbeddedMode() implicitWidth: langbar.width implicitHeight: langbar.height color: "#ffffe3" radius: 5 RowLayout { id: langbar spacing: 10 Text { font.pixelSize: 12 font.family: roboto.name text: qsTr("Language: ") Layout.leftMargin: 30 Layout.topMargin: 10 Layout.bottomMargin: 10 } ComboBox { font.pixelSize: 12 font.family: roboto.name model: imageWriter.getTranslations() Layout.preferredWidth: 200 currentIndex: -1 Component.onCompleted: { currentIndex = find(imageWriter.getCurrentLanguage()) } onActivated: { imageWriter.changeLanguage(editText) } Layout.topMargin: 10 Layout.bottomMargin: 10 } Text { font.pixelSize: 12 font.family: roboto.name text: qsTr("Keyboard: ") Layout.topMargin: 10 Layout.bottomMargin: 10 } ComboBox { enabled: imageWriter.isEmbeddedMode() font.pixelSize: 12 font.family: roboto.name model: imageWriter.getKeymapLayoutList() currentIndex: -1 Component.onCompleted: { currentIndex = find(imageWriter.getCurrentKeyboard()) } onActivated: { imageWriter.changeKeyboard(editText) } Layout.topMargin: 10 Layout.bottomMargin: 10 Layout.rightMargin: 30 } } } /* Language/keyboard bar is normally only visible in embedded mode. To test translations also show it when shift+ctrl+L is pressed. */ Shortcut { sequences: ["Shift+Ctrl+L", "Shift+Meta+L"] context: Qt.ApplicationShortcut onActivated: { langbarRect.visible = true } } } DropArea { anchors.fill: parent onEntered: { if (drag.active && mimeData.hasUrls()) { drag.acceptProposedAction() } } onDropped: { if (drop.urls && drop.urls.length > 0) { onFileSelected(drop.urls[0].toString()) } } } } } Popup { id: hwpopup x: 50 y: 25 width: parent.width-100 height: parent.height-50 padding: 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside property string hwselected: "" // background of title Rectangle { color: "#f5f5f5" anchors.right: parent.right anchors.top: parent.top height: 35 width: parent.width } // line under title Rectangle { color: "#afafaf" width: parent.width y: 35 implicitHeight: 1 } Text { text: "X" anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: 25 anchors.topMargin: 10 font.family: roboto.name font.bold: true MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { hwpopup.close() } } } ColumnLayout { spacing: 10 Text { text: qsTr("Raspberry Pi Device") horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter Layout.fillWidth: true Layout.topMargin: 10 font.family: roboto.name font.bold: true } Item { clip: true Layout.preferredWidth: hwlist.width Layout.preferredHeight: hwlist.height ListView { id: hwlistview interactive: false ListView { id: hwlist model: ListModel { id: deviceModel ListElement { name: qsTr("[ All ]") tags: "[]" icon: "" description: "" matching_type: "exclusive" } } currentIndex: -1 delegate: hwdelegate width: window.width-100 height: window.height-100 boundsBehavior: Flickable.StopAtBounds highlight: Rectangle { color: "lightsteelblue"; radius: 5 } ScrollBar.vertical: ScrollBar { width: 10 policy: hwlist.contentHeight > hwlist.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded } Keys.onSpacePressed: { if (currentIndex != -1) selectHWitem(model.get(currentIndex)) } Accessible.onPressAction: { if (currentIndex != -1) selectHWitem(model.get(currentIndex)) } Keys.onEnterPressed: Keys.onSpacePressed(event) Keys.onReturnPressed: Keys.onSpacePressed(event) } } } } } /* Popup for OS selection */ Popup { id: ospopup x: 50 y: 25 width: parent.width-100 height: parent.height-50 padding: 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside property string categorySelected : "" // background of title Rectangle { color: "#f5f5f5" anchors.right: parent.right anchors.top: parent.top height: 35 width: parent.width } // line under title Rectangle { color: "#afafaf" width: parent.width y: 35 implicitHeight: 1 } Text { text: "X" anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: 25 anchors.topMargin: 10 font.family: roboto.name font.bold: true MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { ospopup.close() osswipeview.decrementCurrentIndex() } } } ColumnLayout { spacing: 10 Text { text: qsTr("Operating System") horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter Layout.fillWidth: true Layout.topMargin: 10 font.family: roboto.name font.bold: true } Item { clip: true Layout.preferredWidth: oslist.width Layout.preferredHeight: oslist.height SwipeView { id: osswipeview interactive: false ListView { id: oslist model: osmodel currentIndex: -1 delegate: osdelegate width: window.width-100 height: window.height-100 boundsBehavior: Flickable.StopAtBounds highlight: Rectangle { color: "lightsteelblue"; radius: 5 } ScrollBar.vertical: ScrollBar { width: 10 policy: oslist.contentHeight > oslist.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded } Keys.onSpacePressed: { if (currentIndex != -1) selectOSitem(model.get(currentIndex), true) } Accessible.onPressAction: { if (currentIndex != -1) selectOSitem(model.get(currentIndex), true) } Keys.onEnterPressed: Keys.onSpacePressed(event) Keys.onReturnPressed: Keys.onSpacePressed(event) } } } } } Component { id: suboslist ListView { model: ListModel { ListElement { url: "" icon: "icons/ic_chevron_left_40px.svg" extract_size: 0 image_download_size: 0 extract_sha256: "" contains_multiple_files: false release_date: "" subitems_url: "internal://back" subitems_json: "" name: qsTr("Back") description: qsTr("Go back to main menu") tooltip: "" website: "" init_format: "" } } currentIndex: -1 delegate: osdelegate width: window.width-100 height: window.height-100 boundsBehavior: Flickable.StopAtBounds highlight: Rectangle { color: "lightsteelblue"; radius: 5 } ScrollBar.vertical: ScrollBar { width: 10 policy: parent.contentHeight > parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded } Keys.onSpacePressed: { if (currentIndex != -1) selectOSitem(model.get(currentIndex)) } Accessible.onPressAction: { if (currentIndex != -1) selectOSitem(model.get(currentIndex)) } Keys.onEnterPressed: Keys.onSpacePressed(event) Keys.onReturnPressed: Keys.onSpacePressed(event) } } ListModel { id: osmodel Component.onCompleted: { if (imageWriter.isOnline()) { fetchOSlist(); } } } Component { id: hwdelegate Item { width: window.width-100 height: contentLayout.implicitHeight + 24 Accessible.name: name+".\n"+description MouseArea { id: hwMouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { bgrect.mouseOver = true } onExited: { bgrect.mouseOver = false } onClicked: { selectHWitem(model) } } Rectangle { id: bgrect anchors.fill: parent color: "#f5f5f5" visible: mouseOver && parent.ListView.view.currentIndex !== index property bool mouseOver: false } Rectangle { id: borderrect implicitHeight: 1 implicitWidth: parent.width color: "#dcdcdc" y: parent.height } RowLayout { id: contentLayout anchors { left: parent.left top: parent.top right: parent.right margins: 12 } spacing: 12 Image { source: icon == "icons/ic_build_48px.svg" ? "icons/cat_misc_utility_images.png": icon Layout.preferredHeight: 40 Layout.preferredWidth: 40 sourceSize.width: 40 sourceSize.height: 40 fillMode: Image.PreserveAspectFit verticalAlignment: Image.AlignVCenter Layout.alignment: Qt.AlignVCenter } ColumnLayout { Layout.fillWidth: true Text { text: name elide: Text.ElideRight font.family: roboto.name font.bold: true } Text { Layout.fillWidth: true font.family: roboto.name text: description wrapMode: Text.WordWrap color: "#1a1a1a" } ToolTip { visible: hwMouseArea.containsMouse && typeof(tooltip) == "string" && tooltip != "" delay: 1000 text: typeof(tooltip) == "string" ? tooltip : "" clip: false } } } } } Component { id: osdelegate Item { width: window.width-100 height: contentLayout.implicitHeight + 24 Accessible.name: name+".\n"+description MouseArea { id: osMouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { bgrect.mouseOver = true } onExited: { bgrect.mouseOver = false } onClicked: { selectOSitem(model) } } Rectangle { id: bgrect anchors.fill: parent color: "#f5f5f5" visible: mouseOver && parent.ListView.view.currentIndex !== index property bool mouseOver: false } Rectangle { id: borderrect implicitHeight: 1 implicitWidth: parent.width color: "#dcdcdc" y: parent.height } RowLayout { id: contentLayout anchors { left: parent.left top: parent.top right: parent.right margins: 12 } spacing: 12 Image { source: icon == "icons/ic_build_48px.svg" ? "icons/cat_misc_utility_images.png": icon Layout.preferredHeight: 40 Layout.preferredWidth: 40 sourceSize.width: 40 sourceSize.height: 40 fillMode: Image.PreserveAspectFit verticalAlignment: Image.AlignVCenter Layout.alignment: Qt.AlignVCenter } ColumnLayout { Layout.fillWidth: true RowLayout { spacing: 12 Text { text: name elide: Text.ElideRight font.family: roboto.name font.bold: true } Image { source: "icons/ic_info_16px.png" Layout.preferredHeight: 16 Layout.preferredWidth: 16 visible: typeof(website) == "string" && website MouseArea { anchors.fill: parent onClicked: Qt.openUrlExternally(website) } } Item { Layout.fillWidth: true } } Text { Layout.fillWidth: true font.family: roboto.name text: description wrapMode: Text.WordWrap color: "#1a1a1a" } Text { Layout.fillWidth: true elide: Text.ElideRight color: "#646464" font.weight: Font.Light visible: typeof(release_date) == "string" && release_date text: qsTr("Released: %1").arg(release_date) } Text { Layout.fillWidth: true elide: Text.ElideRight color: "#646464" font.weight: Font.Light visible: typeof(url) == "string" && url != "" && url != "internal://format" text: !url ? "" : typeof(extract_sha256) != "undefined" && imageWriter.isCached(url,extract_sha256) ? qsTr("Cached on your computer") : url.startsWith("file://") ? qsTr("Local file") : qsTr("Online - %1 GB download").arg((image_download_size/1073741824).toFixed(1)) } ToolTip { visible: osMouseArea.containsMouse && typeof(tooltip) == "string" && tooltip != "" delay: 1000 text: typeof(tooltip) == "string" ? tooltip : "" clip: false } } Image { source: "icons/ic_chevron_right_40px.svg" visible: (typeof(subitems_json) == "string" && subitems_json != "") || (typeof(subitems_url) == "string" && subitems_url != "" && subitems_url != "internal://back") Layout.preferredHeight: 40 Layout.preferredWidth: 40 fillMode: Image.PreserveAspectFit } } } } /* Popup for storage device selection */ Popup { id: dstpopup x: 50 y: 25 width: parent.width-100 height: parent.height-50 padding: 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside onClosed: imageWriter.stopDriveListPolling() // background of title Rectangle { color: "#f5f5f5" anchors.right: parent.right anchors.top: parent.top height: 35 width: parent.width } // line under title Rectangle { color: "#afafaf" width: parent.width y: 35 implicitHeight: 1 } Text { text: "X" anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: 25 anchors.topMargin: 10 font.family: roboto.name font.bold: true MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { dstpopup.close() } } } ColumnLayout { spacing: 10 Text { text: qsTr("Storage") horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter Layout.fillWidth: true Layout.topMargin: 10 font.family: roboto.name font.bold: true } Item { clip: true Layout.preferredWidth: dstlist.width Layout.preferredHeight: dstlist.height ListView { id: dstlist model: driveListModel delegate: dstdelegate width: window.width-100 height: window.height-100 boundsBehavior: Flickable.StopAtBounds highlight: Rectangle { color: "lightsteelblue"; radius: 5 } ScrollBar.vertical: ScrollBar { width: 10 policy: dstlist.contentHeight > dstlist.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded } Keys.onSpacePressed: { if (currentIndex == -1) return selectDstItem(currentItem) } Accessible.onPressAction: { if (currentIndex == -1) return selectDstItem(currentItem) } Keys.onEnterPressed: Keys.onSpacePressed(event) Keys.onReturnPressed: Keys.onSpacePressed(event) } } } } Component { id: dstdelegate Item { width: window.width-100 height: 60 Accessible.name: { var txt = description+" - "+(size/1000000000).toFixed(1)+" gigabytes" if (mountpoints.length > 0) { txt += qsTr("Mounted as %1").arg(mountpoints.join(", ")) } return txt; } property string description: model.description property string device: model.device property string size: model.size Rectangle { id: dstbgrect anchors.fill: parent color: "#f5f5f5" visible: mouseOver && parent.ListView.view.currentIndex !== index property bool mouseOver: false } Rectangle { id: dstborderrect implicitHeight: 1 implicitWidth: parent.width color: "#dcdcdc" y: parent.height } Row { leftPadding: 25 Column { width: 64 Image { source: isUsb ? "icons/ic_usb_40px.svg" : isScsi ? "icons/ic_storage_40px.svg" : "icons/ic_sd_storage_40px.svg" verticalAlignment: Image.AlignVCenter height: parent.parent.parent.height fillMode: Image.Pad } } Column { width: parent.parent.width-64 Text { textFormat: Text.StyledText height: parent.parent.parent.height verticalAlignment: Text.AlignVCenter font.family: roboto.name text: { var sizeStr = (size/1000000000).toFixed(1)+" GB"; var txt; if (isReadOnly) { txt = "

"+description+" - "+sizeStr+"

" txt += "" if (mountpoints.length > 0) { txt += qsTr("Mounted as %1").arg(mountpoints.join(", "))+" " } txt += qsTr("[WRITE PROTECTED]")+"" } else { txt = "

"+description+" - "+sizeStr+"

" if (mountpoints.length > 0) { txt += ""+qsTr("Mounted as %1").arg(mountpoints.join(", "))+"" } } return txt; } } } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { dstbgrect.mouseOver = true } onExited: { dstbgrect.mouseOver = false } onClicked: { selectDstItem(model) } } } } MsgPopup { id: msgpopup } MsgPopup { id: quitpopup continueButton: false yesButton: true noButton: true title: qsTr("Are you sure you want to quit?") text: qsTr("Raspberry Pi Imager is still busy.
Are you sure you want to quit?") onYes: { Qt.quit() } } MsgPopup { id: confirmwritepopup continueButton: false yesButton: true noButton: true title: qsTr("Warning") onYes: { langbarRect.visible = false writebutton.visible = false writebutton.enabled = false customizebutton.visible = false cancelwritebutton.enabled = true cancelwritebutton.visible = true cancelverifybutton.enabled = true progressText.text = qsTr("Preparing to write..."); progressText.visible = true progressBar.visible = true progressBar.indeterminate = true progressBar.Material.accent = "#ffffff" osbutton.enabled = false dstbutton.enabled = false hwbutton.enabled = false imageWriter.setVerifyEnabled(true) imageWriter.startWrite() } function askForConfirmation() { text = qsTr("All existing data on '%1' will be erased.
Are you sure you want to continue?").arg(dstbutton.text) openPopup() } } MsgPopup { id: updatepopup continueButton: false yesButton: true noButton: true property url url title: qsTr("Update available") text: qsTr("There is a newer version of Imager available.
Would you like to visit the website to download it?") onYes: { Qt.openUrlExternally(url) } } OptionsPopup { id: optionspopup } UseSavedSettingsPopup { id: usesavedsettingspopup onYes: { optionspopup.initialize() optionspopup.applySettings() confirmwritepopup.askForConfirmation() } onNo: { confirmwritepopup.askForConfirmation() } onNoClearSettings: { imageWriter.clearSavedCustomizationSettings() confirmwritepopup.askForConfirmation() } onEditSettings: { optionspopup.openPopup() } } /* Utility functions */ function httpRequest(url, callback) { var xhr = new XMLHttpRequest(); xhr.timeout = 5000 xhr.onreadystatechange = (function(x) { return function() { if (x.readyState === x.DONE) { if (x.status === 200) { callback(x) } else { onError(qsTr("Error downloading OS list from Internet")) } } } })(xhr) xhr.open("GET", url) xhr.send() } /* Slots for signals imagewrite emits */ function onDownloadProgress(now,total) { var newPos if (total) { newPos = now/(total+1) } else { newPos = 0 } if (progressBar.value !== newPos) { if (progressText.text === qsTr("Cancelling...")) return progressText.text = qsTr("Writing... %1%").arg(Math.floor(newPos*100)) progressBar.indeterminate = false progressBar.value = newPos } } function onVerifyProgress(now,total) { var newPos if (total) { newPos = now/total } else { newPos = 0 } if (progressBar.value !== newPos) { if (cancelwritebutton.visible) { cancelwritebutton.visible = false cancelverifybutton.visible = true } if (progressText.text === qsTr("Finalizing...")) return progressText.text = qsTr("Verifying... %1%").arg(Math.floor(newPos*100)) progressBar.Material.accent = "#6cc04a" progressBar.value = newPos } } function onPreparationStatusUpdate(msg) { progressText.text = qsTr("Preparing to write... (%1)").arg(msg) } function resetWriteButton() { progressText.visible = false progressBar.visible = false customizebutton.visible = imageWriter.imageSupportsCustomization() osbutton.enabled = true dstbutton.enabled = true hwbutton.enabled = true writebutton.visible = true writebutton.enabled = imageWriter.readyToWrite() cancelwritebutton.visible = false cancelverifybutton.visible = false } function onError(msg) { msgpopup.title = qsTr("Error") msgpopup.text = msg msgpopup.openPopup() resetWriteButton() } function onSuccess() { msgpopup.title = qsTr("Write Successful") if (osbutton.text === qsTr("Erase")) msgpopup.text = qsTr("%1 has been erased

You can now remove the SD card from the reader").arg(dstbutton.text) else if (imageWriter.isEmbeddedMode()) { //msgpopup.text = qsTr("%1 has been written to %2").arg(osbutton.text).arg(dstbutton.text) /* Just reboot to the installed OS */ Qt.quit() } else msgpopup.text = qsTr("%1 has been written to %2

You can now remove the SD card from the reader").arg(osbutton.text).arg(dstbutton.text) if (imageWriter.isEmbeddedMode()) { msgpopup.continueButton = false msgpopup.quitButton = true } msgpopup.openPopup() imageWriter.setDst("") dstbutton.text = qsTr("CHOOSE STORAGE") resetWriteButton() } function onFileSelected(file) { imageWriter.setSrc(file) osbutton.text = imageWriter.srcFileName() ospopup.close() osswipeview.decrementCurrentIndex() if (imageWriter.readyToWrite()) { writebutton.enabled = true } customizebutton.visible = imageWriter.imageSupportsCustomization() } function onCancelled() { resetWriteButton() } function onFinalizing() { progressText.text = qsTr("Finalizing...") } function shuffle(arr) { for (var i = 0; i < arr.length - 1; i++) { var j = i + Math.floor(Math.random() * (arr.length - i)); var t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } function checkForRandom(list) { for (var i in list) { var entry = list[i] if ("subitems" in entry) { checkForRandom(entry["subitems"]) if ("random" in entry && entry["random"]) { shuffle(entry["subitems"]) } } } } function filterItems(list, tags, matchingType) { if (!tags || !tags.length) return var i = list.length while (i--) { var entry = list[i] if ("devices" in entry && entry["devices"].length) { var foundTag = false switch(matchingType) { case 0: /* exact matching */ case 2: /* exact matching */ for (var j in tags) { if (entry["devices"].includes(tags[j])) { foundTag = true break } } /* If there's no match, remove this item from the list. */ if (!foundTag) { list.splice(i, 1) continue } break case 1: /* Exlusive by prefix matching */ case 3: /* Inclusive by prefix matching */ for (var deviceTypePrefix in tags) { for (var deviceSpec in entry["devices"]) { if (deviceSpec.startsWith(deviceTypePrefix)) { foundTag = true break } } /* Terminate outer loop early if we've already * decided it's a match */ if (foundTag) { break } } /* If there's no match, remove this item from the list. */ if (!foundTag) { list.splice(i, 1) continue } break } } else { /* No device list attached? If we're in an exclusive mode that's bad news indeed. */ switch (matchingType) { case 0: case 1: if (!("subitems" in entry)) { /* If you're not carrying subitems, you're not going in. */ list.splice(i, 1) } break case 2: case 3: /* Inclusive filtering. We're keeping this one. */ break; } } if ("subitems" in entry) { filterItems(entry["subitems"], tags, hwTagMatchingType) } } } function oslistFromJson(o) { var oslist = false var lang_country = Qt.locale().name if ("os_list_"+lang_country in o) { oslist = o["os_list_"+lang_country] } else if (lang_country.includes("_")) { var lang = lang_country.substr(0, lang_country.indexOf("_")) if ("os_list_"+lang in o) { oslist = o["os_list_"+lang] } } if (!oslist) { if (!"os_list" in o) { onError(qsTr("Error parsing os_list.json")) return false } oslist = o["os_list"] } if (hwTags != "") { filterItems(oslist, JSON.parse(hwTags), hwTagMatchingType) } checkForRandom(oslist) /* Flatten subitems to subitems_json */ for (var i in oslist) { var entry = oslist[i]; if ("subitems" in entry) { entry["subitems_json"] = JSON.stringify(entry["subitems"]) delete entry["subitems"] } } return oslist } function selectNamedOS(name, collection) { for (var i = 0; i < collection.count; i++) { var os = collection.get(i) if (typeof(os.subitems_json) == "string" && os.subitems_json != "") { selectNamedOS(name, os.subitems_json) } else if (typeof(os.url) !== "undefined" && name === os.name) { selectOSitem(os, false) break } } } function fetchOSlist() { httpRequest(imageWriter.constantOsListUrl(), function (x) { var o = JSON.parse(x.responseText) var oslist = oslistFromJson(o) if (oslist === false) return osmodel.clear() for (var i in oslist) { osmodel.append(oslist[i]) } if ("imager" in o) { var imager = o["imager"] if ("devices" in imager) { deviceModel.clear() var devices = imager["devices"] for (var j in devices) { devices[j]["tags"] = JSON.stringify(devices[j]["tags"]) deviceModel.append(devices[j]) if ("default" in devices[j] && devices[j]["default"]) { hwlist.currentIndex = deviceModel.count-1 } } } if (imageWriter.getBoolSetting("check_version") && "latest_version" in imager && "url" in imager) { if (!imageWriter.isEmbeddedMode() && imageWriter.isVersionNewer(imager["latest_version"])) { updatepopup.url = imager["url"] updatepopup.openPopup() } } if ("default_os" in imager) { selectNamedOS(imager["default_os"], osmodel) } if (imageWriter.isEmbeddedMode()) { if ("embedded_default_os" in imager) { selectNamedOS(imager["embedded_default_os"], osmodel) } if ("embedded_default_destination" in imager) { imageWriter.startDriveListPolling() setDefaultDest.drive = imager["embedded_default_destination"] setDefaultDest.start() } } } /* Add in our 'special' items. */ osmodel.append({ url: "internal://format", icon: "icons/erase.png", extract_size: 0, image_download_size: 0, extract_sha256: "", contains_multiple_files: false, release_date: "", subitems_url: "", subitems_json: "", name: qsTr("Erase"), description: qsTr("Format card as FAT32"), tooltip: "", website: "", init_format: "" }) osmodel.append({ url: "", icon: "icons/use_custom.png", name: qsTr("Use custom"), description: qsTr("Select a custom .img from your computer") }) }) } Timer { /* Verify if default drive is in our list after 100 ms */ id: setDefaultDest property string drive : "" interval: 100 onTriggered: { for (var i = 0; i < driveListModel.rowCount(); i++) { /* FIXME: there should be a better way to iterate drivelist than fetch data by numeric role number */ if (driveListModel.data(driveListModel.index(i,0), 0x101) === drive) { selectDstItem({ device: drive, description: driveListModel.data(driveListModel.index(i,0), 0x102), size: driveListModel.data(driveListModel.index(i,0), 0x103), readonly: false }) break } } } } function newSublist() { if (osswipeview.currentIndex == (osswipeview.count-1)) { var newlist = suboslist.createObject(osswipeview) osswipeview.addItem(newlist) } var m = osswipeview.itemAt(osswipeview.currentIndex+1).model if (m.count>1) { m.remove(1, m.count-1) } return m } function selectHWitem(hwmodel) { hwTags = hwmodel.tags if (hwmodel.matching_type) { switch (hwmodel.matching_type) { case "exclusive": hwTagMatchingType = 0 break; case "exclusive_prefix": hwTagMatchingType = 1 break; case "inclusive": hwTagMatchingType = 2 break; case "inclusive_prefix": hwTagMatchingType = 3 break; } } else { /* Default is exclusive exact matching */ hwTagMatchingType = 0 } /* Reload list */ httpRequest(imageWriter.constantOsListUrl(), function (x) { var o = JSON.parse(x.responseText) var oslist = oslistFromJson(o) if (oslist === false) return osmodel.remove(0, osmodel.count-2) for (var i in oslist) { osmodel.insert(osmodel.count-2, oslist[i]) } }) hwbutton.text = hwmodel.name hwpopup.close() } function selectOSitem(d, selectFirstSubitem) { if (typeof(d.subitems_json) == "string" && d.subitems_json !== "") { var m = newSublist() var subitems = JSON.parse(d.subitems_json) for (var i in subitems) { var entry = subitems[i]; if ("subitems" in entry) { /* Flatten sub-subitems entry */ entry["subitems_json"] = JSON.stringify(entry["subitems"]) delete entry["subitems"] } m.append(entry) } osswipeview.itemAt(osswipeview.currentIndex+1).currentIndex = (selectFirstSubitem === true) ? 0 : -1 osswipeview.incrementCurrentIndex() ospopup.categorySelected = d.name } else if (typeof(d.subitems_url) == "string" && d.subitems_url !== "") { if (d.subitems_url === "internal://back") { osswipeview.decrementCurrentIndex() ospopup.categorySelected = "" } else { ospopup.categorySelected = d.name var suburl = d.subitems_url var m = newSublist() httpRequest(suburl, function (x) { var o = JSON.parse(x.responseText) var oslist = oslistFromJson(o) if (oslist === false) return for (var i in oslist) { m.append(oslist[i]) } }) osswipeview.itemAt(osswipeview.currentIndex+1).currentIndex = (selectFirstSubitem === true) ? 0 : -1 osswipeview.incrementCurrentIndex() } } else if (d.url === "") { if (!imageWriter.isEmbeddedMode()) { imageWriter.openFileDialog() } else { if (imageWriter.mountUsbSourceMedia()) { var m = newSublist() var oslist = JSON.parse(imageWriter.getUsbSourceOSlist()) for (var i in oslist) { m.append(oslist[i]) } osswipeview.itemAt(osswipeview.currentIndex+1).currentIndex = (selectFirstSubitem === true) ? 0 : -1 osswipeview.incrementCurrentIndex() } else { onError(qsTr("Connect an USB stick containing images first.
The images must be located in the root folder of the USB stick.")) } } } else { imageWriter.setSrc(d.url, d.image_download_size, d.extract_size, typeof(d.extract_sha256) != "undefined" ? d.extract_sha256 : "", typeof(d.contains_multiple_files) != "undefined" ? d.contains_multiple_files : false, ospopup.categorySelected, d.name, typeof(d.init_format) != "undefined" ? d.init_format : "") osbutton.text = d.name ospopup.close() osswipeview.decrementCurrentIndex() if (imageWriter.readyToWrite()) { writebutton.enabled = true } customizebutton.visible = imageWriter.imageSupportsCustomization() } } function selectDstItem(d) { if (d.isReadOnly) { onError(qsTr("SD card is write protected.
Push the lock switch on the left side of the card upwards, and try again.")) return } dstpopup.close() imageWriter.setDst(d.device, d.size) dstbutton.text = d.description if (imageWriter.readyToWrite()) { writebutton.enabled = true } } }