diff --git a/src/app/drivemanager.cpp b/src/app/drivemanager.cpp index 0ad1cb34..7b566d97 100644 --- a/src/app/drivemanager.cpp +++ b/src/app/drivemanager.cpp @@ -343,3 +343,178 @@ void Drive::setRestoreStatus(Drive::RestoreStatus o) emit restoreStatusChanged(); } } + +RestoreableDriveManager::RestoreableDriveManager(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSourceModel(DriveManager::instance()); + + connect(DriveManager::instance(), &DriveManager::drivesChanged, this, &RestoreableDriveManager::onSourceModelChanged); + + // Connect to existing drives + connectToDrives(); + + if (rowCount() > 0) { + m_selectedIndex = 0; + } +} + +void RestoreableDriveManager::connectToDrives() +{ + DriveManager *dm = DriveManager::instance(); + for (int i = 0; i < dm->rowCount(); i++) { + QModelIndex idx = dm->index(i, 0); + Drive *drive = qvariant_cast(dm->data(idx, Qt::UserRole + 1)); + if (drive) { + // Use unique connection to avoid duplicates + connect(drive, &Drive::restoreStatusChanged, this, &RestoreableDriveManager::onDriveRestoreStatusChanged, Qt::UniqueConnection); + } + } +} + +void RestoreableDriveManager::onDriveRestoreStatusChanged() +{ + // Store the previously selected drive + Drive *previouslySelected = selected(); + + // Invalidate filter to update which drives are shown + invalidateFilter(); + emit lengthChanged(); + + // Check if the previously selected drive is still in the filtered list + if (previouslySelected) { + bool stillInList = false; + for (int i = 0; i < rowCount(); i++) { + QModelIndex idx = index(i, 0); + Drive *drive = qvariant_cast(data(idx, Qt::UserRole + 1)); + if (drive == previouslySelected) { + stillInList = true; + if (m_selectedIndex != i) { + m_selectedIndex = i; + emit selectedChanged(); + } + break; + } + } + + // If the previously selected drive is no longer in the list, select a new one + if (!stillInList) { + if (rowCount() > 0) { + m_selectedIndex = 0; + } else { + m_selectedIndex = -1; + } + emit selectedChanged(); + } + } else { + // No previous selection - select first drive if available + if (rowCount() > 0) { + m_selectedIndex = 0; + emit selectedChanged(); + } + } +} + +bool RestoreableDriveManager::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + + DriveManager *dm = DriveManager::instance(); + if (source_row < 0 || source_row >= dm->rowCount()) + return false; + + QModelIndex idx = dm->index(source_row, 0); + Drive *drive = qvariant_cast(dm->data(idx, Qt::UserRole + 1)); + + if (drive && drive->restoreStatus() == Drive::CONTAINS_LIVE) + return true; + + return false; +} + +QHash RestoreableDriveManager::roleNames() const +{ + QHash ret; + ret.insert(Qt::UserRole + 1, "drive"); + ret.insert(Qt::UserRole + 2, "display"); + ret.insert(Qt::DisplayRole, "name"); + return ret; +} + +QVariant RestoreableDriveManager::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + QModelIndex sourceIndex = mapToSource(index); + DriveManager *dm = DriveManager::instance(); + + if (role == Qt::UserRole + 1) + return dm->data(sourceIndex, Qt::UserRole + 1); + else if (role == Qt::UserRole + 2 || role == Qt::DisplayRole) { + Drive *drive = qvariant_cast(dm->data(sourceIndex, Qt::UserRole + 1)); + if (drive) + return drive->name(); + } + + return QVariant(); +} + +Drive *RestoreableDriveManager::selected() const +{ + if (m_selectedIndex >= 0 && m_selectedIndex < rowCount()) { + QModelIndex idx = index(m_selectedIndex, 0); + return qvariant_cast(data(idx, Qt::UserRole + 1)); + } + return nullptr; +} + +int RestoreableDriveManager::selectedIndex() const +{ + return m_selectedIndex; +} + +void RestoreableDriveManager::setSelectedIndex(int index) +{ + if (m_selectedIndex != index && index >= 0 && index < rowCount()) { + m_selectedIndex = index; + emit selectedChanged(); + } +} + +int RestoreableDriveManager::length() const +{ + return rowCount(); +} + +void RestoreableDriveManager::onSourceModelChanged() +{ + // Connect to any new drives + connectToDrives(); + + // Remember previous state + int previousCount = rowCount(); + Drive *previousSelected = selected(); + + invalidateFilter(); + + int newCount = rowCount(); + + // Always emit length changed + emit lengthChanged(); + + // Reset selection if out of bounds + if (m_selectedIndex >= newCount) { + m_selectedIndex = newCount > 0 ? 0 : -1; + } + + // If there are restoreable drives and nothing is selected, select the first one + if (m_selectedIndex < 0 && newCount > 0) { + m_selectedIndex = 0; + } + + // Emit selectedChanged if the selected drive changed + if (selected() != previousSelected) { + emit selectedChanged(); + } +} diff --git a/src/app/drivemanager.h b/src/app/drivemanager.h index 4e1d396c..21f3ae99 100644 --- a/src/app/drivemanager.h +++ b/src/app/drivemanager.h @@ -22,6 +22,7 @@ #include #include +#include #include "releasemanager.h" @@ -29,6 +30,7 @@ class DriveManager; class DriveProvider; class Drive; class UdisksDrive; +class RestoreableDriveManager; /** * @brief The DriveManager class @@ -196,4 +198,47 @@ public slots: bool m_delayedWrite{false}; }; +/** + * @brief The RestoreableDriveManager class + * + * A proxy model that filters drives to only show those containing live USB systems. + * + * @property selected the currently selected drive + * @property selectedIndex the index of the currently selected drive + * @property length count of the filtered (restoreable) drives + */ +class RestoreableDriveManager : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(Drive *selected READ selected NOTIFY selectedChanged) + Q_PROPERTY(int selectedIndex READ selectedIndex WRITE setSelectedIndex NOTIFY selectedChanged) + Q_PROPERTY(int length READ length NOTIFY lengthChanged) +public: + explicit RestoreableDriveManager(QObject *parent = nullptr); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + + QHash roleNames() const override; + Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Drive *selected() const; + int selectedIndex() const; + void setSelectedIndex(int index); + + int length() const; + +signals: + void selectedChanged(); + void lengthChanged(); + +private slots: + void onSourceModelChanged(); + void onDriveRestoreStatusChanged(); + +private: + void connectToDrives(); + + int m_selectedIndex{0}; +}; + #endif // DRIVEMANAGER_H diff --git a/src/app/main.cpp b/src/app/main.cpp index 8891b4e3..e72d5bf6 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -64,6 +64,7 @@ int main(int argc, char **argv) engine.rootContext()->setContextProperty("downloadManager", DownloadManager::instance()); engine.rootContext()->setContextProperty("drives", DriveManager::instance()); + engine.rootContext()->setContextProperty("restoreableDrives", new RestoreableDriveManager()); engine.rootContext()->setContextProperty("portalFileDialog", new PortalFileDialog(&app)); engine.rootContext()->setContextProperty("mediawriterVersion", MEDIAWRITER_VERSION); engine.rootContext()->setContextProperty("releases", new ReleaseManager()); diff --git a/src/app/qml/MainPage.qml b/src/app/qml/MainPage.qml index e0f54e11..3c0d5a62 100644 --- a/src/app/qml/MainPage.qml +++ b/src/app/qml/MainPage.qml @@ -46,21 +46,20 @@ Page { QQC2.RadioButton { id: restoreRadio - visible: drives.lastRestoreable - text: drives.lastRestoreable ? qsTr("Restore %1").arg(drives.lastRestoreable.name) : "" + visible: restoreableDrives.length > 0 + text: { + if (restoreableDrives.length === 0) + return "" + else if (restoreableDrives.length === 1 && restoreableDrives.selected) + return qsTr("Restore %1").arg(restoreableDrives.selected.name) + else if (restoreableDrives.length > 1) + return qsTr("Restore a USB drive (%1 available)").arg(restoreableDrives.length) + else + return qsTr("Restore a USB drive") + } onClicked: { selectedOption = Units.MainSelect.Restore } - - Connections { - target: drives - function onLastRestoreableChanged() { - if (drives.lastRestoreable != null && !restoreRadio.visible) - restoreRadio.visible = true - if (!drives.lastRestoreable) - restoreRadio.visible = false - } - } } // HACK: enforces all the items above to move up and make smaller diff --git a/src/app/qml/RestorePage.qml b/src/app/qml/RestorePage.qml index b98ce448..4ed70f9a 100644 --- a/src/app/qml/RestorePage.qml +++ b/src/app/qml/RestorePage.qml @@ -25,12 +25,49 @@ import QtQuick.Layouts 6.6 Page { id: restorePage - text: qsTr("Restore Drive %1").arg(lastRestoreable.name) + property var restoringDrive: null + property string restoringDriveName: "" + + text: { + if (restoringDriveName) + return qsTr("Restore Drive %1").arg(restoringDriveName) + else if (restoreableDrives.selected) + return qsTr("Restore Drive %1").arg(restoreableDrives.selected.name) + else + return qsTr("Restore Drive") + } textLevel: 1 + ColumnLayout { + id: driveSelectionColumn + visible: restoreableDrives.length > 0 && !restoringDrive + Layout.fillWidth: true + + QQC2.Label { + text: qsTr("Select USB Drive to Restore:") + font.bold: true + } + + QQC2.ComboBox { + id: driveCombo + Layout.fillWidth: true + model: restoreableDrives + enabled: restoreableDrives.length > 0 + displayText: currentIndex === -1 || restoreableDrives.length === 0 ? + qsTr("No drives with live systems connected") : currentText + textRole: "name" + currentIndex: restoreableDrives.selectedIndex + onCurrentIndexChanged: { + if (currentIndex >= 0 && currentIndex !== restoreableDrives.selectedIndex) { + restoreableDrives.selectedIndex = currentIndex + } + } + } + } + QQC2.Label { id: warningText - visible: lastRestoreable.restoreStatus == Units.RestoreStatus.Contains_Live + visible: restoreableDrives.selected && restoreableDrives.selected.restoreStatus == Units.RestoreStatus.Contains_Live && !restoringDrive Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true text: qsTr("

To reclaim all space available on the drive, it has to be restored to its factory settings. The live system and all saved data will be deleted.

You don't need to restore the drive if you want to write another live system to it.

Do you want to restore it to factory settings?

" ) @@ -40,7 +77,7 @@ Page { ColumnLayout { id: progress - visible: lastRestoreable.restoreStatus == Units.RestoreStatus.Restoring + visible: restoringDrive && restoringDrive.restoreStatus == Units.RestoreStatus.Restoring Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true @@ -63,7 +100,7 @@ Page { QQC2.Label { id: restoredText - visible: lastRestoreable.restoreStatus == Units.RestoreStatus.Restored + visible: restoringDrive && restoringDrive.restoreStatus == Units.RestoreStatus.Restored Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true text: qsTr("Your drive was successfully restored!") @@ -72,47 +109,60 @@ Page { QQC2.Label { id: errorText - visible: lastRestoreable.restoreStatus == Units.RestoreStatus.Restore_Error + visible: restoringDrive && restoringDrive.restoreStatus == Units.RestoreStatus.Restore_Error Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true text: qsTr("Unfortunately, an error occurred during the process. Please try restoring the drive using your system tools.") wrapMode: QQC2.Label.Wrap } - Component.onCompleted: { - lastRestoreable = drives.lastRestoreable + function startRestore() { + if (restoreableDrives.selected) { + restoringDrive = restoreableDrives.selected + restoringDriveName = restoreableDrives.selected.name + restoringDrive.restore() + } } + function finishRestore() { + restoringDrive = null + restoringDriveName = "" + selectedPage = Units.Page.MainPage + } + states: [ State { name: "restored" - when: lastRestoreable.restoreStatus == Units.RestoreStatus.Restored + when: restoringDrive && restoringDrive.restoreStatus == Units.RestoreStatus.Restored PropertyChanges { - target: mainWindow; + target: mainWindow title: qsTr("Restoring finished") } - StateChangeScript { - script: drives.lastRestoreable = null - } } ] - previousButtonEnabled: lastRestoreable.restoreStatus != Units.RestoreStatus.Restored && - lastRestoreable.restoreStatus != Units.RestoreStatus.Restoring + previousButtonEnabled: !restoringDrive || (restoringDrive.restoreStatus != Units.RestoreStatus.Restoring && restoringDrive.restoreStatus != Units.RestoreStatus.Restored) previousButtonVisible: previousButtonEnabled onPreviousButtonClicked: { + restoringDrive = null + restoringDriveName = "" selectedPage = Units.Page.MainPage } - nextButtonEnabled: lastRestoreable.restoreStatus == Units.RestoreStatus.Restored || - lastRestoreable.restoreStatus == Units.RestoreStatus.Contains_Live - nextButtonVisible: lastRestoreable.restoreStatus != Units.RestoreStatus.Restoring - nextButtonText: lastRestoreable.restoreStatus == Units.RestoreStatus.Restored ? qsTr("Finish") : qsTr("Restore") + nextButtonEnabled: { + if (restoringDrive) { + return restoringDrive.restoreStatus == Units.RestoreStatus.Restored || + restoringDrive.restoreStatus == Units.RestoreStatus.Restore_Error + } + return restoreableDrives.selected && restoreableDrives.selected.restoreStatus == Units.RestoreStatus.Contains_Live + } + nextButtonVisible: !restoringDrive || restoringDrive.restoreStatus != Units.RestoreStatus.Restoring + nextButtonText: restoringDrive && (restoringDrive.restoreStatus == Units.RestoreStatus.Restored || restoringDrive.restoreStatus == Units.RestoreStatus.Restore_Error) ? qsTr("Finish") : qsTr("Restore") onNextButtonClicked: { - if (lastRestoreable.restoreStatus == Units.RestoreStatus.Restored) - selectedPage = Units.Page.MainPage + if (restoringDrive && (restoringDrive.restoreStatus == Units.RestoreStatus.Restored || restoringDrive.restoreStatus == Units.RestoreStatus.Restore_Error)) + finishRestore() else - drives.lastRestoreable.restore() + startRestore() } } diff --git a/src/app/qml/main.qml b/src/app/qml/main.qml index c0ba08c4..15358ad9 100644 --- a/src/app/qml/main.qml +++ b/src/app/qml/main.qml @@ -31,7 +31,6 @@ ApplicationWindow { property int selectedPage: Units.Page.MainPage property int selectedVersion: Units.Source.Product property int selectedOption: Units.MainSelect.Download - property QtObject lastRestoreable property bool eraseVariant: false StackView { @@ -144,14 +143,6 @@ ApplicationWindow { } ] } - - Connections { - target: drives - function onLastRestoreableChanged() { - if (!drives.selected) - selectedPage = Units.Page.MainPage - } - } Units { id: units