WIP: Tabbed options interface

This commit is contained in:
Floris Bos 2023-09-22 23:51:36 +02:00
parent 4c71a2294e
commit 6eb358ed75
3 changed files with 343 additions and 313 deletions

View file

@ -5,20 +5,20 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import QtQuick.Layouts 1.0 import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.2 import QtQuick.Controls.Material 2.2
import QtQuick.Window 2.15
import "qmlcomponents" import "qmlcomponents"
Popup { Window {
id: popup id: popup
//x: 62 width: cl.implicitWidth+cl.spacing
x: (parent.width-width)/2 minimumWidth: width
y: 10 maximumWidth: width
//width: parent.width-125 minimumHeight: 125
width: popupbody.implicitWidth+60 height: Math.min(750, cl.implicitHeight)
height: parent.height-20 title: qsTr("Advanced options")
padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
property bool initialized: false property bool initialized: false
property bool hasSavedSettings: false property bool hasSavedSettings: false
property string config property string config
@ -29,57 +29,11 @@ Popup {
property string cloudinitwrite property string cloudinitwrite
property string cloudinitnetwork property string cloudinitnetwork
// 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: {
initialized = false
popup.close()
}
}
}
ColumnLayout { ColumnLayout {
id: cl
spacing: 20 spacing: 20
anchors.fill: parent anchors.fill: parent
Text {
id: popupheader
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
Layout.topMargin: 10
font.family: roboto.name
font.bold: true
text: qsTr("Advanced options")
}
ScrollView { ScrollView {
id: popupbody id: popupbody
font.family: roboto.name font.family: roboto.name
@ -89,15 +43,13 @@ Popup {
Layout.leftMargin: 25 Layout.leftMargin: 25
Layout.topMargin: 10 Layout.topMargin: 10
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn //ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ColumnLayout { ColumnLayout {
GroupBox { RowLayout {
title: qsTr("Image customization options")
label: RowLayout {
Label { Label {
text: parent.parent.title text: qsTr("Image customization options")
} }
ComboBox { ComboBox {
id: comboSaveSettings id: comboSaveSettings
@ -111,281 +63,317 @@ Popup {
} }
} }
ColumnLayout { TabBar {
spacing: -10 id: bar
Layout.fillWidth: true
RowLayout { TabButton {
ImCheckBox { text: qsTr("General")
id: chkHostname }
text: qsTr("Set hostname:") TabButton {
onCheckedChanged: { text: qsTr("Services")
if (checked) { }
fieldHostname.forceActiveFocus() TabButton {
text: qsTr("Options")
}
}
GroupBox {
StackLayout {
width: parent.width
currentIndex: bar.currentIndex
ColumnLayout {
// General tab
spacing: -10
RowLayout {
ImCheckBox {
id: chkHostname
text: qsTr("Set hostname:")
onCheckedChanged: {
if (checked) {
fieldHostname.forceActiveFocus()
}
}
}
TextField {
id: fieldHostname
enabled: chkHostname.checked
text: "raspberrypi"
validator: RegularExpressionValidator { regularExpression: /[0-9A-Za-z][0-9A-Za-z-]{0,62}/ }
}
Text {
text : ".local"
color: chkHostname.checked ? "black" : "grey"
} }
} }
}
TextField {
id: fieldHostname ImCheckBox {
enabled: chkHostname.checked id: chkSetUser
text: "raspberrypi" text: qsTr("Set username and password")
validator: RegularExpressionValidator { regularExpression: /[0-9A-Za-z][0-9A-Za-z-]{0,62}/ } onCheckedChanged: {
} if (!checked && chkSSH.checked && radioPasswordAuthentication.checked) {
Text { checked = true;
text : ".local"
color: chkHostname.checked ? "black" : "grey"
}
}
ImCheckBox {
id: chkSSH
text: qsTr("Enable SSH")
onCheckedChanged: {
if (checked) {
if (!radioPasswordAuthentication.checked && !radioPubKeyAuthentication.checked) {
radioPasswordAuthentication.checked = true
}
if (radioPasswordAuthentication.checked) {
chkSetUser.checked = true
if (!fieldUserPassword.length) {
fieldUserPassword.forceActiveFocus()
} }
} }
} }
}
}
ColumnLayout {
enabled: chkSSH.checked
Layout.leftMargin: 40
spacing: -10
ImRadioButton { ColumnLayout {
id: radioPasswordAuthentication enabled: chkSetUser.checked
text: qsTr("Use password authentication") Layout.leftMargin: 40
onCheckedChanged: { spacing: -5
if (checked) {
chkSetUser.checked = true GridLayout {
fieldUserPassword.forceActiveFocus() columns: 2
} columnSpacing: 10
} rowSpacing: -5
}
ImRadioButton { Text {
id: radioPubKeyAuthentication text: qsTr("Username:")
text: qsTr("Allow public-key authentication only") color: parent.enabled ? (fieldUserName.indicateError ? "red" : "black") : "grey"
onCheckedChanged: {
if (checked) {
if (chkSetUser.checked && fieldUserName.text == "pi" && fieldUserPassword.text.length == 0) {
chkSetUser.checked = false
} }
fieldPublicKey.forceActiveFocus() TextField {
} id: fieldUserName
} text: "pi"
} Layout.minimumWidth: 200
GridLayout { property bool indicateError: false
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
enabled: radioPubKeyAuthentication.checked
Text { onTextEdited: {
text: qsTr("Set authorized_keys for '%1':").arg(fieldUserName.text) indicateError = false
color: parent.enabled ? "black" : "grey" }
textFormat: Text.PlainText
}
TextField {
id: fieldPublicKey
Layout.minimumWidth: 200
}
}
}
ImCheckBox {
id: chkSetUser
text: qsTr("Set username and password")
onCheckedChanged: {
if (!checked && chkSSH.checked && radioPasswordAuthentication.checked) {
checked = true;
}
}
}
ColumnLayout {
enabled: chkSetUser.checked
Layout.leftMargin: 40
spacing: -5
GridLayout {
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("Username:")
color: parent.enabled ? (fieldUserName.indicateError ? "red" : "black") : "grey"
}
TextField {
id: fieldUserName
text: "pi"
Layout.minimumWidth: 200
property bool indicateError: false
onTextEdited: {
indicateError = false
}
}
Text {
text: qsTr("Password:")
color: parent.enabled ? (fieldUserPassword.indicateError ? "red" : "black") : "grey"
}
TextField {
id: fieldUserPassword
echoMode: TextInput.Password
Layout.minimumWidth: 200
property bool alreadyCrypted: false
property bool indicateError: false
onTextEdited: {
if (alreadyCrypted) {
/* User is trying to edit saved
(crypted) password, clear field */
alreadyCrypted = false
clear()
} }
if (indicateError) {
Text {
text: qsTr("Password:")
color: parent.enabled ? (fieldUserPassword.indicateError ? "red" : "black") : "grey"
}
TextField {
id: fieldUserPassword
echoMode: TextInput.Password
Layout.minimumWidth: 200
property bool alreadyCrypted: false
property bool indicateError: false
onTextEdited: {
if (alreadyCrypted) {
/* User is trying to edit saved
(crypted) password, clear field */
alreadyCrypted = false
clear()
}
if (indicateError) {
indicateError = false
}
}
}
}
}
ImCheckBox {
id: chkWifi
text: qsTr("Configure wireless LAN")
onCheckedChanged: {
if (checked) {
if (!fieldWifiSSID.length) {
fieldWifiSSID.forceActiveFocus()
} else if (!fieldWifiPassword.length) {
fieldWifiPassword.forceActiveFocus()
}
}
}
}
GridLayout {
enabled: chkWifi.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("SSID:")
color: parent.enabled ? (fieldWifiSSID.indicateError ? "red" : "black") : "grey"
}
TextField {
id: fieldWifiSSID
Layout.minimumWidth: 200
property bool indicateError: false
onTextEdited: {
indicateError = false indicateError = false
} }
} }
}
}
}
ImCheckBox { ImCheckBox {
id: chkWifi id: chkWifiSSIDHidden
text: qsTr("Configure wireless LAN") Layout.columnSpan: 2
onCheckedChanged: { text: qsTr("Hidden SSID")
if (checked) { checked: false
if (!fieldWifiSSID.length) { }
fieldWifiSSID.forceActiveFocus()
} else if (!fieldWifiPassword.length) { Text {
fieldWifiPassword.forceActiveFocus() text: qsTr("Password:")
color: parent.enabled ? (fieldWifiPassword.indicateError ? "red" : "black") : "grey"
}
TextField {
id: fieldWifiPassword
Layout.minimumWidth: 200
echoMode: chkShowPassword.checked ? TextInput.Normal : TextInput.Password
property bool indicateError: false
onTextEdited: {
indicateError = false
}
}
ImCheckBox {
id: chkShowPassword
Layout.columnSpan: 2
text: qsTr("Show password")
checked: true
}
Text {
text: qsTr("Wireless LAN country:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldWifiCountry
editable: true
}
}
ImCheckBox {
id: chkLocale
text: qsTr("Set locale settings")
}
GridLayout {
enabled: chkLocale.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("Time zone:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldTimezone
editable: true
Layout.minimumWidth: 200
}
Text {
text: qsTr("Keyboard layout:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldKeyboardLayout
editable: true
Layout.minimumWidth: 200
} }
} }
} }
}
GridLayout {
enabled: chkWifi.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text { ColumnLayout {
text: qsTr("SSID:") // Remote access tab
color: parent.enabled ? (fieldWifiSSID.indicateError ? "red" : "black") : "grey" spacing: -10
}
TextField { ImCheckBox {
id: fieldWifiSSID id: chkSSH
Layout.minimumWidth: 200 text: qsTr("Enable SSH")
property bool indicateError: false onCheckedChanged: {
onTextEdited: { if (checked) {
indicateError = false if (!radioPasswordAuthentication.checked && !radioPubKeyAuthentication.checked) {
radioPasswordAuthentication.checked = true
}
if (radioPasswordAuthentication.checked) {
chkSetUser.checked = true
if (!fieldUserPassword.length) {
fieldUserPassword.forceActiveFocus()
}
}
}
}
}
ColumnLayout {
enabled: chkSSH.checked
Layout.leftMargin: 40
spacing: -10
ImRadioButton {
id: radioPasswordAuthentication
text: qsTr("Use password authentication")
onCheckedChanged: {
if (checked) {
chkSetUser.checked = true
//fieldUserPassword.forceActiveFocus()
}
}
}
ImRadioButton {
id: radioPubKeyAuthentication
text: qsTr("Allow public-key authentication only")
onCheckedChanged: {
if (checked) {
if (chkSetUser.checked && fieldUserName.text == "pi" && fieldUserPassword.text.length == 0) {
chkSetUser.checked = false
}
fieldPublicKey.forceActiveFocus()
}
}
}
GridLayout {
Layout.leftMargin: 40
columns: 1
columnSpacing: 10
rowSpacing: -5
enabled: radioPubKeyAuthentication.checked
Text {
text: qsTr("Set authorized_keys for '%1':").arg(fieldUserName.text)
color: parent.enabled ? "black" : "grey"
textFormat: Text.PlainText
}
TextArea {
id: fieldPublicKey
wrapMode: TextEdit.WrapAnywhere
Layout.minimumWidth: 400
}
ImButton {
text: qsTr("RUN SSH-KEYGEN")
enabled: imageWriter.hasSshKeyGen() && !imageWriter.hasPubKey()
onClicked: {
enabled = false
imageWriter.generatePubKey()
fieldPublicKey.text = imageWriter.getDefaultPubKey()
}
}
}
} }
} }
ImCheckBox { ColumnLayout {
id: chkWifiSSIDHidden // Options tab
Layout.columnSpan: 2 spacing: -10
text: qsTr("Hidden SSID")
checked: false
}
Text { ImCheckBox {
text: qsTr("Password:") id: chkBeep
color: parent.enabled ? (fieldWifiPassword.indicateError ? "red" : "black") : "grey" text: qsTr("Play sound when finished")
} }
TextField { ImCheckBox {
id: fieldWifiPassword id: chkEject
Layout.minimumWidth: 200 text: qsTr("Eject media when finished")
echoMode: chkShowPassword.checked ? TextInput.Normal : TextInput.Password }
property bool indicateError: false ImCheckBox {
onTextEdited: { id: chkTelemtry
indicateError = false text: qsTr("Enable telemetry")
} }
}
ImCheckBox {
id: chkShowPassword
Layout.columnSpan: 2
text: qsTr("Show password")
checked: true
}
Text {
text: qsTr("Wireless LAN country:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldWifiCountry
editable: true
}
}
ImCheckBox {
id: chkLocale
text: qsTr("Set locale settings")
}
GridLayout {
enabled: chkLocale.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("Time zone:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldTimezone
editable: true
Layout.minimumWidth: 200
}
Text {
text: qsTr("Keyboard layout:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldKeyboardLayout
editable: true
Layout.minimumWidth: 200
} }
} }
} }
} }
GroupBox {
title: qsTr("Persistent settings")
Layout.fillWidth: true
ColumnLayout {
spacing: -10
ImCheckBox {
id: chkBeep
text: qsTr("Play sound when finished")
}
ImCheckBox {
id: chkEject
text: qsTr("Eject media when finished")
}
ImCheckBox {
id: chkTelemtry
text: qsTr("Enable telemetry")
}
}
}
}
} }
RowLayout { RowLayout {
@ -400,12 +388,14 @@ Popup {
{ {
fieldUserPassword.indicateError = true fieldUserPassword.indicateError = true
fieldUserPassword.forceActiveFocus() fieldUserPassword.forceActiveFocus()
bar.currentIndex = 0
return return
} }
if (chkSetUser.checked && fieldUserName.text.length == 0) if (chkSetUser.checked && fieldUserName.text.length == 0)
{ {
fieldUserName.indicateError = true fieldUserName.indicateError = true
fieldUserName.forceActiveFocus() fieldUserName.forceActiveFocus()
bar.currentIndex = 0
return return
} }
@ -423,6 +413,7 @@ Popup {
} }
if (fieldWifiSSID.indicateError || fieldWifiPassword.indicateError) if (fieldWifiSSID.indicateError || fieldWifiPassword.indicateError)
{ {
bar.currentIndex = 0
return return
} }
} }
@ -559,7 +550,9 @@ Popup {
initialize() initialize()
} }
open() //open()
show()
raise()
popupbody.forceActiveFocus() popupbody.forceActiveFocus()
} }

View file

@ -790,10 +790,22 @@ QByteArray ImageWriter::getUsbSourceOSlist()
#endif #endif
} }
QString ImageWriter::_pubKeyFileName()
{
return QDir::homePath()+"/.ssh/id_rsa.pub";
}
QString ImageWriter::_privKeyFileName()
{
QString fn = _pubKeyFileName();
fn.chop(4);
return fn;
}
QString ImageWriter::getDefaultPubKey() QString ImageWriter::getDefaultPubKey()
{ {
QByteArray pubkey; QByteArray pubkey;
QFile pubfile(QDir::homePath()+"/.ssh/id_rsa.pub"); QFile pubfile(_pubKeyFileName());
if (pubfile.exists() && pubfile.open(QFile::ReadOnly)) if (pubfile.exists() && pubfile.open(QFile::ReadOnly))
{ {
@ -804,6 +816,26 @@ QString ImageWriter::getDefaultPubKey()
return pubkey; return pubkey;
} }
bool ImageWriter::hasPubKey()
{
return QFile::exists(_pubKeyFileName());
}
bool ImageWriter::hasSshKeyGen()
{
return true;
}
void ImageWriter::generatePubKey()
{
if (!hasPubKey() && !QFile::exists(_privKeyFileName()))
{
QStringList args;
args << "-t" << "rsa" << "-f" << _privKeyFileName() << "-N" << "";
QProcess::execute("ssh-keygen", args);
}
}
QString ImageWriter::getTimezone() QString ImageWriter::getTimezone()
{ {
return QTimeZone::systemTimeZoneId(); return QTimeZone::systemTimeZoneId();

View file

@ -98,6 +98,9 @@ public:
/* Functions to collect information from computer running imager to make image customization easier */ /* Functions to collect information from computer running imager to make image customization easier */
Q_INVOKABLE QString getDefaultPubKey(); Q_INVOKABLE QString getDefaultPubKey();
Q_INVOKABLE bool hasPubKey();
Q_INVOKABLE bool hasSshKeyGen();
Q_INVOKABLE void generatePubKey();
Q_INVOKABLE QString getTimezone(); Q_INVOKABLE QString getTimezone();
Q_INVOKABLE QStringList getTimezoneList(); Q_INVOKABLE QStringList getTimezoneList();
Q_INVOKABLE QStringList getCountryList(); Q_INVOKABLE QStringList getCountryList();
@ -178,6 +181,8 @@ protected:
#endif #endif
void _parseCompressedFile(); void _parseCompressedFile();
QString _pubKeyFileName();
QString _privKeyFileName();
}; };
#endif // IMAGEWRITER_H #endif // IMAGEWRITER_H