/*
* 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
import Qt.labs.settings 1.0
ApplicationWindow {
id: window
visible: true
width: 680
height: 420
minimumWidth: 680
maximumWidth: 680
minimumHeight: 420
maximumHeight: 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
msgpopupheader.text = qsTr("Are you sure you want to quit?")
msgpopupbody.text = qsTr("Imager is still busy.
Are you sure you want to quit?")
quitbutton.visible = true
msgpopup.open()
}
}
ColumnLayout {
id: bg
spacing: 0
Image {
id: image
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.preferredWidth: window.width
fillMode: Image.PreserveAspectFit
source: "icons/rpi2.png"
}
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: 3
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()
Material.background: "#ffffff"
Material.foreground: "#c51a4a"
}
}
ColumnLayout {
id: columnLayout2
spacing: 0
Layout.fillWidth: true
Text {
id: text2
color: "#ffffff"
text: qsTr("SD Card")
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 SD CARD")
font.family: roboto.name
Layout.minimumHeight: 40
Layout.preferredWidth: 100
Layout.fillWidth: true
onClicked: {
imageWriter.refreshDriveList()
drivePollTimer.start()
dstpopup.open()
}
Material.background: "#ffffff"
Material.foreground: "#c51a4a"
}
}
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
enabled: false
Material.background: "#ffffff"
Material.foreground: "#c51a4a"
onClicked: {
if (!imageWriter.readyToWrite())
return;
enabled = false
cancelwritebutton.enabled = true
cancelwritebutton.visible = true
cancelverifybutton.enabled = true
progressText.text = qsTr("Writing... %1%").arg("0")
progressText.visible = true
progressBar.visible = true
progressBar.indeterminate = true
progressBar.Material.accent = "#ffffff"
osbutton.enabled = false
dstbutton.enabled = false
imageWriter.setVerifyEnabled(true)
imageWriter.startWrite()
}
}
}
ColumnLayout {
id: columnLayout3
Layout.columnSpan: 3
Layout.alignment: Qt.AlignHCenter | 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
}
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
}
}
}
}
}
/*
Popup for OS selection
*/
Popup {
id: ospopup
x: 50
y: 25
width: parent.width-100
height: parent.height-50
padding: 0
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
// 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
width: oslist.width
height: oslist.height
SwipeView {
id: osswipeview
interactive: false
ListView {
id: oslist
model: osmodel
delegate: osdelegate
width: window.width-100
height: window.height-100
focus: true
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
width: 10
policy: oslist.contentHeight > oslist.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
}
}
ListView {
id: suboslist
model: subosmodel
delegate: osdelegate
width: window.width-100
height: window.height-100
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
width: 10
policy: suboslist.contentHeight > suboslist.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
}
}
}
}
}
}
ListModel {
id: osmodel
ListElement {
url: "internal://format"
icon: "icons/ic_delete_40px.png"
extract_size: 0
image_download_size: 0
extract_sha256: ""
contains_multiple_files: false
release_date: ""
subitems_url: ""
subitems: []
name: qsTr("Erase")
description: qsTr("Format card as FAT32")
tooltip: ""
}
ListElement {
url: ""
icon: "icons/ic_computer_40px.png"
name: qsTr("Use custom")
description: qsTr("Select a custom .img from your computer")
}
Component.onCompleted: {
httpRequest(imageWriter.constantOsListUrl(), function (x) {
var o = JSON.parse(x.responseText)
if (!"os_list" in o) {
onError(qsTr("Error parsing os_list.json"))
return;
}
var oslist = o["os_list"]
for (var i in oslist) {
osmodel.insert(osmodel.count-2, oslist[i])
}
})
}
}
ListModel {
id: subosmodel
ListElement {
url: ""
icon: "icons/ic_chevron_left_40px.png"
extract_size: 0
image_download_size: 0
extract_sha256: ""
contains_multiple_files: false
release_date: ""
subitems_url: "internal://back"
subitems: []
name: qsTr("Back")
description: qsTr("Go back to main menu")
tooltip: ""
}
}
Component {
id: osdelegate
Item {
width: window.width-100
height: image_download_size ? 100 : 60
Rectangle {
id: bgrect
anchors.fill: parent
color: "#f5f5f5"
visible: 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/ic_build_40px.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 = "
"+name+"
" txt += ""+description+"" if (typeof(release_date) == "string" && release_date) txt += ""+description+" - "+(size/1000000000).toFixed(1)+" GB"+"
" 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.visible = true } onExited: { dstbgrect.visible = false } onClicked: { drivePollTimer.stop() dstpopup.close() imageWriter.setDst(device, size) dstbutton.text = description if (imageWriter.readyToWrite()) { writebutton.enabled = true } } } } } Popup { id: msgpopup x: 75 y: parent.height/2-100 width: parent.width-150 height: msgpopupbody.implicitHeight+150 //200 padding: 0 focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside // 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 { id: msgx 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: { if (continuebutton.visible) msgpopup.close() else Qt.quit() } } } ColumnLayout { spacing: 20 anchors.fill: parent Text { id: msgpopupheader horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter Layout.fillWidth: true Layout.topMargin: 10 font.family: roboto.name font.bold: true } Text { id: msgpopupbody font.pointSize: 12 wrapMode: Text.Wrap textFormat: Text.StyledText font.family: roboto.name Layout.maximumWidth: msgpopup.width-50 Layout.fillHeight: true Layout.leftMargin: 25 Layout.topMargin: 25 } RowLayout { Layout.alignment: Qt.AlignCenter | Qt.AlignBottom Layout.bottomMargin: 10 spacing: 20 Button { id: quitbutton text: qsTr("QUIT APP") onClicked: Qt.quit() Material.foreground: "#ffffff" Material.background: "#c51a4a" font.family: roboto.name visible: false } Button { id: continuebutton text: qsTr("CONTINUE") onClicked: { msgpopup.close() quitbutton.visible = false } Material.foreground: "#ffffff" Material.background: "#c51a4a" font.family: roboto.name } Text { text: " " } } } } /* Persistent settings */ Settings { category: "General" property alias x: window.x property alias y: window.y } /* Timer for polling drivelist changes */ Timer { id: drivePollTimer repeat: true onTriggered: imageWriter.refreshDriveList() } /* 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 resetWriteButton() { progressText.visible = false progressBar.visible = false osbutton.enabled = true dstbutton.enabled = true writebutton.visible = true writebutton.enabled = imageWriter.readyToWrite() cancelwritebutton.visible = false cancelverifybutton.visible = false } function onError(msg) { msgpopupheader.text = qsTr("Error") msgpopupbody.text = msg msgpopup.open() resetWriteButton() } function onSuccess() { msgpopupheader.text = qsTr("Write Successful") if (osbutton.text === qsTr("Erase")) msgpopupbody.text = qsTr("%2 has been erased