Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ if(NOT BUILD_CLI_ONLY)
wizard/dialogs/ConfirmSystemDriveDialog.qml
wizard/dialogs/KeychainPermissionDialog.qml
wizard/dialogs/UpdateAvailableDialog.qml
wizard/dialogs/RepositoryDialog.qml
wizard/dialogs/AppOptionsDialog.qml
wizard/LanguageSelectionStep.qml
qmlcomponents/BaseDialog.qml
Expand All @@ -448,6 +449,7 @@ if(NOT BUILD_CLI_ONLY)
qmlcomponents/ImPopup.qml
qmlcomponents/ImRadioButton.qml
qmlcomponents/ImOptionPill.qml
qmlcomponents/ImOptionButton.qml
qmlcomponents/ImTextField.qml
qmlcomponents/SelectionListView.qml
qmlcomponents/OSSelectionListView.qml
Expand Down
2 changes: 1 addition & 1 deletion src/qmlcomponents/BaseDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Dialog {
function registerFocusGroup(name, getItemsFn, order) {
dialogFocusScope.registerFocusGroup(name, getItemsFn, order)
}

function rebuildFocusOrder() {
dialogFocusScope.rebuildFocusOrder()
}
Expand Down
91 changes: 91 additions & 0 deletions src/qmlcomponents/ImOptionButton.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2025 Raspberry Pi Ltd
*/

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import RpiImager
import QtQuick.Controls.Material 2.15

// A labeled button styled for Imager; only the button clicks, not the whole row
Item {
id: control
property alias text: label.text
property bool checked: false
// Optional help link next to the label
property string helpLabel: ""
property url helpUrl: ""
property string btnText: ""
signal clicked()

// Expose the actual focusable control for tab navigation
property alias focusItem: optionButton

implicitHeight: Math.max(Style.buttonHeightStandard - 8, 28)
implicitWidth: label.implicitWidth + optionButton.implicitWidth + Style.cardPadding

RowLayout {
anchors.fill: parent
spacing: Style.spacingMedium

// Text block (label + optional help) on the left
ColumnLayout {
id: textColumn
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: Style.spacingXXSmall

// Main label
Text {
id: label
Layout.alignment: Qt.AlignVCenter
font.family: Style.fontFamilyBold
font.pixelSize: Style.fontSizeFormLabel
font.bold: true
color: Style.formLabelColor
elide: Text.ElideRight
TapHandler { onTapped: sw.toggle() }
}

// Optional help link under the label
Text {
id: helpText
Layout.alignment: Qt.AlignVCenter
visible: control.helpLabel !== "" && control.helpUrl !== ""
text: control.helpLabel
font.family: Style.fontFamily
font.pixelSize: Style.fontSizeDescription
color: Style.buttonForegroundColor
font.underline: helpHover.hovered
Accessible.name: text
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: Qt.openUrlExternally(pill.helpUrl)
}
HoverHandler {
id: helpHover
acceptedDevices: PointerDevice.Mouse
cursorShape: Qt.PointingHandCursor
}
}
}

// Flexible spacer to push the switch flush-right and align across rows
Item { Layout.fillWidth: true }

ImButton {
id: optionButton
Layout.alignment: Qt.AlignVCenter
activeFocusOnTab: true
text: btnText

onClicked: {
control.clicked()
}
}
}

function forceActiveFocus() { optionButton.forceActiveFocus() }
}
24 changes: 17 additions & 7 deletions src/qmlcomponents/ImRadioButton.qml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2022 Raspberry Pi Ltd
* Copyright (C) 2022-2025 Raspberry Pi Ltd
*/

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material

RadioButton {
Material.accent: Style.formControlActiveColor
Expand All @@ -24,10 +24,20 @@ RadioButton {

Keys.onPressed: (event) => {
if (event.key === Qt.Key_Space) {
toggle()
if (!checked) // prevent unchecking the current one
click() // goes through the normal “mouse click” path
event.accepted = true
}
}
Keys.onEnterPressed: toggle()
Keys.onReturnPressed: toggle()
Keys.onEnterPressed: (event) => {
if (!checked)
click()
event.accepted = true
}

Keys.onReturnPressed: (event) => {
if (!checked)
click()
event.accepted = true
}
}
19 changes: 9 additions & 10 deletions src/wizard/DeviceSelectionStep.qml
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,15 @@ WizardStepBase {
if (!root || !root.hwModel) {
return
}
// Only reload if we haven't loaded yet or if the model is empty
if (!modelLoaded || root.hwModel.rowCount() === 0) {
console.log("DeviceSelectionStep: OS list prepared, reloading hardware model")
var success = root.hwModel.reload()
if (success) {
modelLoaded = true
// Set initial focus for keyboard navigation if no device is selected
if (hwlist.currentIndex === -1 && root.hwModel.rowCount() > 0) {
hwlist.currentIndex = 0
}

// Don't guard with modelLoaded to support reloading when repo changed
console.log("DeviceSelectionStep: OS list prepared, reloading hardware model")
var success = root.hwModel.reload()
if (success) {
modelLoaded = true
// Set initial focus for keyboard navigation if no device is selected
if (hwlist.currentIndex === -1 && root.hwModel.rowCount() > 0) {
hwlist.currentIndex = 0
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/wizard/WizardContainer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ Item {
if (!root.optionsPopup.wizardContainer) {
root.optionsPopup.wizardContainer = root
}
// TODO: actually duplicate
// as onOpen in it already calls initialize()
root.optionsPopup.initialize()
root.optionsPopup.open()
}
Expand Down
101 changes: 27 additions & 74 deletions src/wizard/dialogs/AppOptionsDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ BaseDialog {
property var wizardContainer: null

property bool initialized: false
property url selectedRepo: ""
property url originalRepo: ""

// Custom escape handling
function escapePressed() {
Expand All @@ -34,24 +32,14 @@ BaseDialog {
// Register focus groups when component is ready
Component.onCompleted: {
registerFocusGroup("options", function(){
return [chkBeep.focusItem, chkEject.focusItem, chkTelemetry.focusItem, chkDisableWarnings.focusItem]
return [chkBeep.focusItem, chkEject.focusItem, chkTelemetry.focusItem,
chkDisableWarnings.focusItem, editRepoButton.focusItem]
}, 0)
registerFocusGroup("repository", function(){
return [fieldCustomRepository, browseButton]
}, 1)
registerFocusGroup("buttons", function(){
return [cancelButton, saveButton]
}, 2)
}

Connections {
target: imageWriter
// Handle native file selection for "Use custom"
function onFileSelected(fileUrl) {
popup.selectedRepo = fileUrl;
}
}

// Header
Text {
text: qsTr("App Options")
Expand Down Expand Up @@ -120,37 +108,26 @@ BaseDialog {
}
}

RowLayout {
ImOptionButton {
id: editRepoButton
text: qsTr("Content Repository")
btnText: qsTr("Edit")
Layout.fillWidth: true
spacing: Style.spacingMedium

ImTextField {
id: fieldCustomRepository
text: selectedRepo !== "" ? UrlFmt.display(selectedRepo) : ""
Layout.fillWidth: true
placeholderText: qsTr("Select custom Repository")
font.pixelSize: Style.fontSizeInput
readOnly: true
activeFocusOnTab: true
Component.onCompleted: {
focusItem.activeFocusOnTab = true
}

ImButton {
id: browseButton
text: qsTr("Browse")
Layout.minimumWidth: 80
activeFocusOnTab: true
onClicked: {
// Prefer native file dialog via Imager's wrapper, but only if available
if (imageWriter.nativeFileDialogAvailable()) {
// Defer opening the native dialog until after the current event completes
Qt.callLater(function () {
imageWriter.openFileDialog(qsTr("Select Repository"), CommonStrings.repoFiltersString);
});
} else {
// Fallback to QML dialog (forced non-native)
repoFileDialog.open();
}
onClicked: {
// TODO: close this dialog - open sub dialog - show optin between default hardcoded and custom repo and for custom repo browser local or url
// verify repo on save - don't persist longer then app is running
//popup.close()
//Qt.callLater(function () {
//open
//});
if (!repoDialog.wizardContainer) {
repoDialog.wizardContainer = popup.wizardContainer
}
//repoDialog.initialize()
repoDialog.open()
}
}
}
Expand Down Expand Up @@ -200,6 +177,13 @@ BaseDialog {
}
}

RepositoryDialog {
id: repoDialog
parent: popup.parent
imageWriter: popup.imageWriter
wizardContainer: popup.wizardContainer
}

function initialize() {
if (!initialized) {
// Load current settings from ImageWriter
Expand All @@ -208,13 +192,6 @@ BaseDialog {
chkTelemetry.checked = imageWriter.getBoolSetting("telemetry");
// Do not load from QSettings; keep ephemeral
chkDisableWarnings.checked = popup.wizardContainer ? popup.wizardContainer.disableWarnings : false;
if (imageWriter.customRepo()) {
selectedRepo = imageWriter.constantOsListUrl();
originalRepo = selectedRepo;
} else {
selectedRepo = "";
originalRepo = "";
}

initialized = true;
// Pre-compute final height before opening to avoid first-show reflow
Expand All @@ -231,16 +208,6 @@ BaseDialog {
// Do not persist disable_warnings; set ephemeral flag only
if (popup.wizardContainer)
popup.wizardContainer.disableWarnings = chkDisableWarnings.checked;
// Only save repository setting if it has actually changed
if (popup.selectedRepo !== popup.originalRepo) {
if (popup.selectedRepo !== "") {
imageWriter.refreshOsListFrom(selectedRepo);
} else {
// User cleared the repository - reset to default
// Note: setCustomRepo with the default URL will reset to default behavior
imageWriter.setCustomRepo(Qt.resolvedUrl("https://downloads.raspberrypi.org/os_list_imagingutility_v4.json"));
}
}
}

onOpened: {
Expand Down Expand Up @@ -323,18 +290,4 @@ BaseDialog {
}
}
}

property alias repoFileDialog: repoFileDialog

ImFileDialog {
id: repoFileDialog
dialogTitle: qsTr("Select custom repository")
nameFilters: CommonStrings.repoFiltersList
onAccepted: {
popup.selectedRepo = selectedFile;
}
onRejected:
// No-op; user cancelled
{}
}
}
}
Loading