mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-18 07:55:21 +01:00
Fixes "Can't assign to existing role 'subitems' of different type" or hang (depending on Qt version) on using nested subitems. Keep nested subitems flattenend as json string in memory while not being used, to simplify dealing with ListElement expectation that all elements have the same data type, as well as QML/Ecmascript's memory management.
1301 lines
44 KiB
QML
1301 lines
44 KiB
QML
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
|
|
*/
|
|
|
|
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
|
|
|
|
ApplicationWindow {
|
|
id: window
|
|
visible: true
|
|
|
|
width: imageWriter.isEmbeddedMode() ? -1 : 680
|
|
height: imageWriter.isEmbeddedMode() ? -1 : 420
|
|
minimumWidth: imageWriter.isEmbeddedMode() ? -1 : 680
|
|
//maximumWidth: imageWriter.isEmbeddedMode() ? -1 : 680
|
|
minimumHeight: imageWriter.isEmbeddedMode() ? -1 : 420
|
|
//maximumHeight: 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"}
|
|
|
|
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/2
|
|
|
|
Image {
|
|
id: image
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
|
fillMode: Image.PreserveAspectFit
|
|
source: window.height > 700 ? "icons/rpi2-hires.png" : "icons/rpi2.png"
|
|
width: window.width
|
|
height: window.height/2
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
color: "#c31c4a"
|
|
implicitWidth: window.width
|
|
implicitHeight: window.height/2
|
|
|
|
GridLayout {
|
|
id: gridLayout
|
|
rowSpacing: 25
|
|
|
|
anchors.fill: parent
|
|
anchors.topMargin: 25
|
|
anchors.rightMargin: 50
|
|
anchors.leftMargin: 50
|
|
|
|
rows: 6
|
|
columns: 3
|
|
columnSpacing: 25
|
|
|
|
ColumnLayout {
|
|
id: columnLayout
|
|
spacing: 0
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
id: text1
|
|
color: "#ffffff"
|
|
text: qsTr("Operating System")
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 17
|
|
Layout.preferredWidth: 100
|
|
font.pixelSize: 12
|
|
font.family: robotoBold.name
|
|
font.bold: true
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
|
|
Button {
|
|
id: osbutton
|
|
text: imageWriter.srcFileName() === "" ? qsTr("CHOOSE OS") : imageWriter.srcFileName()
|
|
font.family: roboto.name
|
|
spacing: 0
|
|
padding: 0
|
|
bottomPadding: 0
|
|
topPadding: 0
|
|
Layout.minimumHeight: 40
|
|
Layout.fillWidth: true
|
|
onClicked: {
|
|
ospopup.open()
|
|
osswipeview.currentItem.forceActiveFocus()
|
|
}
|
|
Material.background: "#ffffff"
|
|
Material.foreground: "#c51a4a"
|
|
Accessible.ignored: ospopup.visible || dstpopup.visible
|
|
Accessible.description: qsTr("Select this button to change the operating system")
|
|
Accessible.onPressAction: clicked()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: columnLayout2
|
|
spacing: 0
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
id: text2
|
|
color: "#ffffff"
|
|
text: qsTr("Storage")
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 17
|
|
Layout.preferredWidth: 100
|
|
font.pixelSize: 12
|
|
font.family: robotoBold.name
|
|
font.bold: true
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
|
|
Button {
|
|
id: dstbutton
|
|
text: qsTr("CHOOSE STORAGE")
|
|
font.family: roboto.name
|
|
Layout.minimumHeight: 40
|
|
Layout.preferredWidth: 100
|
|
Layout.fillWidth: true
|
|
onClicked: {
|
|
imageWriter.startDriveListPolling()
|
|
dstpopup.open()
|
|
dstlist.forceActiveFocus()
|
|
}
|
|
Material.background: "#ffffff"
|
|
Material.foreground: "#c51a4a"
|
|
Accessible.ignored: ospopup.visible || dstpopup.visible
|
|
Accessible.description: qsTr("Select this button to change the destination storage device")
|
|
Accessible.onPressAction: clicked()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: 0
|
|
Layout.fillWidth: true
|
|
|
|
Text {
|
|
text: " "
|
|
Layout.preferredHeight: 17
|
|
Layout.preferredWidth: 100
|
|
}
|
|
|
|
Button {
|
|
id: writebutton
|
|
text: qsTr("WRITE")
|
|
font.family: roboto.name
|
|
Layout.minimumHeight: 40
|
|
Layout.fillWidth: true
|
|
Accessible.ignored: ospopup.visible || dstpopup.visible
|
|
Accessible.description: qsTr("Select this button to start writing the image")
|
|
|
|
enabled: false
|
|
Material.background: "#ffffff"
|
|
Material.foreground: "#c51a4a"
|
|
onClicked: {
|
|
if (!imageWriter.readyToWrite()) {
|
|
return
|
|
}
|
|
|
|
if (!optionspopup.initialized && imageWriter.imageSupportsCustomization() && imageWriter.hasSavedCustomizationSettings()) {
|
|
usesavedsettingspopup.openPopup()
|
|
} else {
|
|
confirmwritepopup.askForConfirmation()
|
|
}
|
|
}
|
|
Accessible.onPressAction: clicked()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: columnLayout3
|
|
Layout.columnSpan: 3
|
|
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
|
|
|
Text {
|
|
id: progressText
|
|
font.pointSize: 10
|
|
color: "white"
|
|
font.family: robotoBold.name
|
|
font.bold: true
|
|
visible: false
|
|
horizontalAlignment: Text.AlignHCenter
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
ProgressBar {
|
|
id: progressBar
|
|
Layout.fillWidth: true
|
|
visible: false
|
|
Material.background: "#d15d7d"
|
|
}
|
|
|
|
Button {
|
|
id: cancelwritebutton
|
|
text: qsTr("CANCEL WRITE")
|
|
onClicked: {
|
|
enabled = false
|
|
progressText.text = qsTr("Cancelling...")
|
|
imageWriter.cancelWrite()
|
|
}
|
|
Material.background: "#ffffff"
|
|
Material.foreground: "#c51a4a"
|
|
Layout.alignment: Qt.AlignRight
|
|
visible: false
|
|
font.family: roboto.name
|
|
Accessible.onPressAction: clicked()
|
|
}
|
|
Button {
|
|
id: cancelverifybutton
|
|
text: qsTr("CANCEL VERIFY")
|
|
onClicked: {
|
|
enabled = false
|
|
progressText.text = qsTr("Finalizing...")
|
|
imageWriter.setVerifyEnabled(false)
|
|
}
|
|
Material.background: "#ffffff"
|
|
Material.foreground: "#c51a4a"
|
|
Layout.alignment: Qt.AlignRight
|
|
visible: false
|
|
font.family: roboto.name
|
|
Accessible.onPressAction: clicked()
|
|
}
|
|
|
|
Button {
|
|
Layout.bottomMargin: 25
|
|
padding: 5
|
|
id: customizebutton
|
|
onClicked: {
|
|
optionspopup.openPopup()
|
|
}
|
|
Material.background: "#ffffff"
|
|
visible: false
|
|
Accessible.description: qsTr("Select this button to access advanced settings")
|
|
Accessible.onPressAction: clicked()
|
|
|
|
contentItem: Image {
|
|
source: "icons/ic_cog_red.svg"
|
|
fillMode: Image.PreserveAspectFit
|
|
}
|
|
}
|
|
}
|
|
|
|
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: <tab> navigate to next button <space> press button/select item <arrow up/down> go up/down in lists")
|
|
}
|
|
|
|
RowLayout {
|
|
id: langbar
|
|
Layout.columnSpan: 3
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
|
Layout.bottomMargin: 5
|
|
spacing: 10
|
|
visible: imageWriter.isEmbeddedMode()
|
|
|
|
Rectangle {
|
|
anchors.fill: langbar
|
|
color: "#ffffe3"
|
|
radius: 5
|
|
}
|
|
|
|
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: {
|
|
langbar.visible = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
|
|
ListModel {
|
|
id: osmodel
|
|
|
|
ListElement {
|
|
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: ""
|
|
}
|
|
|
|
ListElement {
|
|
url: ""
|
|
icon: "icons/use_custom.png"
|
|
name: qsTr("Use custom")
|
|
description: qsTr("Select a custom .img from your computer")
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (imageWriter.isOnline()) {
|
|
fetchOSlist();
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: osdelegate
|
|
|
|
Item {
|
|
width: window.width-100
|
|
height: image_download_size ? 100 : 60
|
|
Accessible.name: name+".\n"+description
|
|
|
|
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
|
|
}
|
|
|
|
Row {
|
|
leftPadding: 25
|
|
|
|
Column {
|
|
width: 64
|
|
|
|
Image {
|
|
source: icon == "icons/ic_build_48px.svg" ? "icons/cat_misc_utility_images.png": icon
|
|
verticalAlignment: Image.AlignVCenter
|
|
height: parent.parent.parent.height
|
|
fillMode: Image.Pad
|
|
}
|
|
Text {
|
|
text: " "
|
|
// visible: !icon
|
|
}
|
|
}
|
|
Column {
|
|
width: parent.parent.width-64-50-25
|
|
|
|
Text {
|
|
verticalAlignment: Text.AlignVCenter
|
|
height: parent.parent.parent.height
|
|
font.family: roboto.name
|
|
textFormat: Text.RichText
|
|
text: {
|
|
var txt = "<p style='margin-bottom: 5px; font-weight: bold;'>"+name
|
|
if (typeof(website) == "string" && website) {
|
|
txt += " <a href='"+website+"'> <img src='icons/ic_info_16px.png' align='top'></a>"
|
|
}
|
|
txt += "</p><font color='#1a1a1a'>"+description+"</font><font style='font-weight: 200' color='#646464'>"
|
|
if (typeof(release_date) == "string" && release_date)
|
|
txt += "<br>"+qsTr("Released: %1").arg(release_date)
|
|
if (typeof(url) == "string" && url != "" && url != "internal://format") {
|
|
if (typeof(extract_sha256) != "undefined" && imageWriter.isCached(url,extract_sha256)) {
|
|
txt += "<br>"+qsTr("Cached on your computer")
|
|
} else if (url.startsWith("file://")) {
|
|
txt += "<br>"+qsTr("Local file")
|
|
} else {
|
|
txt += "<br>"+qsTr("Online - %1 GB download").arg((image_download_size/1073741824).toFixed(1));
|
|
}
|
|
}
|
|
txt += "</font>";
|
|
|
|
return txt;
|
|
}
|
|
id: osText
|
|
|
|
/*
|
|
Accessible.role: Accessible.ListItem
|
|
Accessible.name: name+".\n"+description
|
|
Accessible.focusable: true
|
|
Accessible.focused: parent.parent.parent.ListView.view.currentIndex === index
|
|
*/
|
|
|
|
ToolTip {
|
|
visible: osMouseArea.containsMouse && typeof(tooltip) == "string" && tooltip != ""
|
|
delay: 1000
|
|
text: typeof(tooltip) == "string" ? tooltip : ""
|
|
clip: false
|
|
}
|
|
}
|
|
|
|
}
|
|
Column {
|
|
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")
|
|
height: parent.parent.parent.height
|
|
fillMode: Image.Pad
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: osMouseArea
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
hoverEnabled: true
|
|
|
|
onEntered: {
|
|
bgrect.mouseOver = true
|
|
}
|
|
|
|
onExited: {
|
|
bgrect.mouseOver = false
|
|
}
|
|
|
|
onClicked: {
|
|
if (osText.hoveredLink) {
|
|
Qt.openUrlExternally(osText.hoveredLink)
|
|
} else {
|
|
selectOSitem(model)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = "<p><font size='4' color='grey'>"+description+" - "+sizeStr+"</font></p>"
|
|
txt += "<font color='grey'>"
|
|
if (mountpoints.length > 0) {
|
|
txt += qsTr("Mounted as %1").arg(mountpoints.join(", "))+" "
|
|
}
|
|
txt += qsTr("[WRITE PROTECTED]")+"</font>"
|
|
} else {
|
|
txt = "<p><font size='4'>"+description+" - "+sizeStr+"</font></p>"
|
|
if (mountpoints.length > 0) {
|
|
txt += "<font color='grey'>"+qsTr("Mounted as %1").arg(mountpoints.join(", "))+"</font>"
|
|
}
|
|
}
|
|
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.<br>Are you sure you want to quit?")
|
|
onYes: {
|
|
Qt.quit()
|
|
}
|
|
}
|
|
|
|
MsgPopup {
|
|
id: confirmwritepopup
|
|
continueButton: false
|
|
yesButton: true
|
|
noButton: true
|
|
title: qsTr("Warning")
|
|
onYes: {
|
|
langbar.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
|
|
imageWriter.setVerifyEnabled(true)
|
|
imageWriter.startWrite()
|
|
}
|
|
|
|
function askForConfirmation()
|
|
{
|
|
text = qsTr("All existing data on '%1' will be erased.<br>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.<br>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: {
|
|
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
|
|
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("<b>%1</b> has been erased<br><br>You can now remove the SD card from the reader").arg(dstbutton.text)
|
|
else if (imageWriter.isEmbeddedMode()) {
|
|
//msgpopup.text = qsTr("<b>%1</b> has been written to <b>%2</b>").arg(osbutton.text).arg(dstbutton.text)
|
|
/* Just reboot to the installed OS */
|
|
Qt.quit()
|
|
}
|
|
else
|
|
msgpopup.text = qsTr("<b>%1</b> has been written to <b>%2</b><br><br>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()
|
|
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 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"]
|
|
}
|
|
|
|
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
|
|
for (var i in oslist) {
|
|
osmodel.insert(osmodel.count-2, oslist[i])
|
|
}
|
|
|
|
if ("imager" in o) {
|
|
var imager = o["imager"]
|
|
if ("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()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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 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.<br>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()
|
|
if (imageWriter.readyToWrite()) {
|
|
writebutton.enabled = true
|
|
}
|
|
customizebutton.visible = imageWriter.imageSupportsCustomization()
|
|
}
|
|
}
|
|
|
|
function selectDstItem(d) {
|
|
if (d.isReadOnly) {
|
|
onError(qsTr("SD card is write protected.<br>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
|
|
}
|
|
}
|
|
}
|