feat: Introduce Device Sidebar Widget and Settings Management

- Removed TabWidget
- Added DeviceSidebarWidget and DeviceSidebarItem classes for managing device navigation in a sidebar format.
- Removed the obsolete DeviceTabWidget class and its associated files.
- Updated MainWindow to integrate DeviceManagerWidget for device management.
- Implemented SettingsManager and SettingsWidget for user-configurable settings.
- Enhanced the main application to support settings loading and saving.
- Updated UI to accommodate new settings and device management features.
This commit is contained in:
uncor3
2025-09-12 18:31:56 +00:00
parent 34dc83c5c5
commit 6fe6245be9
20 changed files with 1380 additions and 527 deletions
+3 -3
View File
@@ -124,9 +124,9 @@ void AppContext::removeDevice(QString _udid)
{
const std::string uuid = _udid.toStdString();
if (!m_devices.contains(uuid)) {
warn("Device with UUID " + _udid +
" not found. Please report this issue.",
"Error");
qDebug() << "Device with UUID " + _udid +
" not found. Please report this issue.",
"Error";
return;
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef CLICKABLEWIDGET_H
#define CLICKABLEWIDGET_H
#include <QMouseEvent>
#include <QWidget>
class ClickableWidget : public QWidget
{
Q_OBJECT
public:
explicit ClickableWidget(QWidget *parent = nullptr) : QWidget(parent) {}
signals:
void clicked();
protected:
// Override the protected event handler
void mousePressEvent(QMouseEvent *event) override
{
// When the event happens, emit our public signal
emit clicked();
// Call the base class implementation if needed
QWidget::mousePressEvent(event);
}
};
#endif // CLICKABLEWIDGET_H
+30 -21
View File
@@ -2,6 +2,7 @@
#include "appcontext.h"
#include "devdiskmanager.h"
#include "iDescriptor.h"
#include "settingsmanager.h"
#include <QCloseEvent>
#include <QComboBox>
#include <QCoreApplication>
@@ -88,9 +89,10 @@ void DevDiskImagesWidget::setupUi()
m_imageListWidget = new QListWidget(this);
m_stackedWidget->addWidget(m_imageListWidget);
m_downloadPath =
QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages");
m_downloadPathEdit->setText(m_downloadPath);
// m_downloadPath =
// QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages");
m_downloadPathEdit->setText(
SettingsManager::sharedInstance()->devdiskimgpath());
displayImages();
}
@@ -145,8 +147,8 @@ void DevDiskImagesWidget::displayImages()
// Parse images using manager
GetImagesSortedFinalResult sortedResult =
DevDiskManager::sharedInstance()->parseImageList(
QByteArray(), m_downloadPath, deviceMajorVersion,
deviceMinorVersion, m_mounted_sig, m_mounted_sig_len);
deviceMajorVersion, deviceMinorVersion, m_mounted_sig,
m_mounted_sig_len);
auto compatibleImages = sortedResult.compatibleImages;
auto otherImages = sortedResult.otherImages;
@@ -242,7 +244,9 @@ void DevDiskImagesWidget::onDownloadButtonClicked()
QString version = button->property("version").toString();
QString versionPath = QDir(m_downloadPath).filePath(version);
QString versionPath =
QDir(SettingsManager::sharedInstance()->devdiskimgpath())
.filePath(version);
if (QDir(versionPath).exists()) {
auto reply = QMessageBox::question(
this, "Confirm Overwrite",
@@ -281,7 +285,9 @@ void DevDiskImagesWidget::startDownload(const QString &version)
progressBar->setVisible(true);
progressBar->setValue(0);
QString targetDir = QDir(m_downloadPath).filePath(version);
QString targetDir =
QDir(SettingsManager::sharedInstance()->devdiskimgpath())
.filePath(version);
if (!QDir().mkpath(targetDir)) {
QMessageBox::critical(
this, "Error",
@@ -384,7 +390,9 @@ void DevDiskImagesWidget::onFileDownloadFinished()
QFileInfo fileInfo(path);
QString filename = fileInfo.fileName();
QString targetPath =
QDir(QDir(m_downloadPath).filePath(item->version)).filePath(filename);
QDir(QDir(SettingsManager::sharedInstance()->devdiskimgpath())
.filePath(item->version))
.filePath(filename);
QFile file(targetPath);
if (!file.open(QIODevice::WriteOnly)) {
@@ -470,8 +478,8 @@ void DevDiskImagesWidget::mountImage(const QString &version)
return;
}
if (!DevDiskManager::sharedInstance()->isImageDownloaded(version,
m_downloadPath)) {
if (!DevDiskManager::sharedInstance()->isImageDownloaded(
version, SettingsManager::sharedInstance()->devdiskimgpath())) {
QMessageBox::warning(
this, "Image Not Found",
QString("The selected disk image for version %1 is not downloaded. "
@@ -483,8 +491,8 @@ void DevDiskImagesWidget::mountImage(const QString &version)
m_mountButton->setEnabled(false);
m_mountButton->setText("Mounting...");
bool success = DevDiskManager::sharedInstance()->mountImage(version, udid,
m_downloadPath);
bool success = DevDiskManager::sharedInstance()->mountImage(
version, udid);
m_mountButton->setEnabled(true);
m_mountButton->setText("Mount");
@@ -503,14 +511,15 @@ void DevDiskImagesWidget::mountImage(const QString &version)
void DevDiskImagesWidget::changeDownloadDirectory()
{
QString dir = QFileDialog::getExistingDirectory(
this, "Select Download Directory", m_downloadPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isEmpty() && dir != m_downloadPath) {
m_downloadPath = dir;
m_downloadPathEdit->setText(m_downloadPath);
displayImages();
}
// TODO: logic moved to settings manager
// QString dir = QFileDialog::getExistingDirectory(
// this, "Select Download Directory", m_downloadPath,
// QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
// if (!dir.isEmpty() && dir != m_downloadPath) {
// m_downloadPath = dir;
// m_downloadPathEdit->setText(m_downloadPath);
// displayImages();
// }
}
void DevDiskImagesWidget::closeEvent(QCloseEvent *event)
@@ -609,4 +618,4 @@ void DevDiskImagesWidget::checkMountedImage()
// QMessageBox::information(this, "Not Mounted",
// "The device has no mounted images.");
}
}
-1
View File
@@ -67,7 +67,6 @@ private:
QPushButton *m_mountButton;
QPushButton *m_check_mountedButton;
QString m_downloadPath;
iDescriptorDevice *m_currentDevice;
QStringList m_compatibleVersions;
QStringList m_otherVersions;
+129 -55
View File
@@ -1,5 +1,6 @@
#include "devdiskmanager.h"
#include "iDescriptor.h"
#include "settingsmanager.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
@@ -71,24 +72,19 @@ QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
return imageFiles;
}
GetImagesSortedFinalResult DevDiskManager::parseImageList(
const QByteArray &jsonData, const QString &downloadPath,
int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig,
uint64_t mounted_sig_len)
GetImagesSortedFinalResult
DevDiskManager::parseImageList(int deviceMajorVersion, int deviceMinorVersion,
const char *mounted_sig,
uint64_t mounted_sig_len)
{
m_availableImages.clear();
QStringList compatibleVersions = QStringList();
QStringList otherVersions = QStringList();
// TODO : wtf is this ?
if (!jsonData.isEmpty()) {
m_imageListJsonData = jsonData;
}
QMap<QString, QMap<QString, QString>> imageFiles = parseDiskDir();
GetImagesSortedResult sortedResult =
getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion,
downloadPath, mounted_sig, mounted_sig_len);
mounted_sig, mounted_sig_len);
sortVersions(sortedResult);
QList<ImageInfo> compatibleResult;
@@ -129,8 +125,7 @@ void DevDiskManager::sortVersions(GetImagesSortedResult &sortedResult)
GetImagesSortedResult DevDiskManager::getImagesSorted(
QMap<QString, QMap<QString, QString>> imageFiles, int deviceMajorVersion,
int deviceMinorVersion, const QString &downloadPath,
const char *mounted_sig, uint64_t mounted_sig_len)
int deviceMinorVersion, const char *mounted_sig, uint64_t mounted_sig_len)
{
QStringList compatibleVersions = QStringList();
@@ -148,7 +143,8 @@ GetImagesSortedResult DevDiskManager::getImagesSorted(
info.version = version;
info.dmgPath = it.value()["DeveloperDiskImage.dmg"];
info.sigPath = it.value()["DeveloperDiskImage.dmg.signature"];
info.isDownloaded = isImageDownloaded(version, downloadPath);
info.isDownloaded = isImageDownloaded(
version, SettingsManager::sharedInstance()->devdiskimgpath());
// Determine compatibility
if (hasConnectedDevice) {
@@ -170,7 +166,10 @@ GetImagesSortedResult DevDiskManager::getImagesSorted(
// Check if mounted
if (info.isCompatible && info.isDownloaded && mounted_sig) {
QString sigLocalPath =
QDir(QDir(downloadPath).filePath(version))
QDir(
QDir(
SettingsManager::sharedInstance()->devdiskimgpath())
.filePath(version))
.filePath("DeveloperDiskImage.dmg.signature");
info.isMounted =
compareSignatures(sigLocalPath.toUtf8().constData(),
@@ -241,8 +240,82 @@ bool DevDiskManager::isImageDownloaded(const QString &version,
return QFile::exists(dmgPath) && QFile::exists(sigPath);
}
bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device,
const QString &downloadPath)
bool DevDiskManager::downloadCompatibleImageInternal(iDescriptorDevice *device)
{
GetImagesSortedFinalResult images = parseImageList(15, 0, "", 0);
for (const ImageInfo &info : images.compatibleImages) {
if (info.isDownloaded) {
qDebug() << "There is a compatible image already downloaded:"
<< info.version;
return true;
}
}
// If none are downloaded, download the newest compatible one
if (!images.compatibleImages.isEmpty()) {
const QString versionToDownload =
images.compatibleImages.first().version;
qDebug() << "No compatible image found locally. Downloading version:"
<< versionToDownload;
QPair<QNetworkReply *, QNetworkReply *> replies =
downloadImage(versionToDownload);
auto *downloadItem = new DownloadItem();
downloadItem->version = versionToDownload;
downloadItem->downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
downloadItem->dmgReply = replies.first;
downloadItem->sigReply = replies.second;
connect(downloadItem->dmgReply, &QNetworkReply::downloadProgress, this,
&DevDiskManager::onDownloadProgress);
connect(downloadItem->dmgReply, &QNetworkReply::finished, this,
&DevDiskManager::onFileDownloadFinished);
connect(downloadItem->sigReply, &QNetworkReply::downloadProgress, this,
&DevDiskManager::onDownloadProgress);
connect(downloadItem->sigReply, &QNetworkReply::finished, this,
&DevDiskManager::onFileDownloadFinished);
m_activeDownloads[downloadItem->dmgReply] = downloadItem;
m_activeDownloads[downloadItem->sigReply] = downloadItem;
return true; // Indicate that the async operation has started
}
qDebug() << "No compatible image found to mount on device:"
<< device->udid.c_str();
return false;
}
bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
{
if (m_isImageListReady) {
// If the list is already fetched, run the logic immediately.
return downloadCompatibleImageInternal(device);
} else {
// Otherwise, connect to the signal and wait.
qDebug() << "Image list not ready, waiting for it to be fetched...";
connect(
this, &DevDiskManager::imageListFetched, this,
[this, device](bool success) {
if (success) {
qDebug() << "Image list is now ready. Retrying download...";
downloadCompatibleImageInternal(device);
} else {
qDebug() << "Failed to fetch image list. Cannot download.";
}
},
Qt::SingleShotConnection);
// The operation is now asynchronous, the immediate return value
// indicates that the process has started.
return true;
}
}
// TODO: boolean to download
bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
{
GetMountedImageResult res = getMountedImage(device->udid.c_str());
if (res.success) {
@@ -252,20 +325,23 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device,
}
GetImagesSortedFinalResult images =
parseImageList(m_imageListJsonData, downloadPath, 15, 0,
res.output.c_str(), res.output.length());
parseImageList(15, 0, res.output.c_str(), res.output.length());
// // 1. Try to mount an already downloaded compatible image
// for (const ImageInfo &info : images.compatibleImages) {
// if (info.isDownloaded) {
// if (mountImage(info.version, device->udid.c_str(), downloadPath))
// {
// qDebug() << "Mounted existing image version" << info.version
// << "on device:" << device->udid.c_str();
// return true;
// }
// }
// }
// 1. Try to mount an already downloaded compatible image
for (const ImageInfo &info : images.compatibleImages) {
if (info.isDownloaded) {
qDebug() << "There is a compatible image already downloaded:"
<< info.version;
return true;
if (mountImage(info.version, device->udid.c_str())) {
qDebug() << "Mounted existing image version" << info.version
<< "on device:" << device->udid.c_str();
return true;
}
}
}
const QString downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
// 2. If none are downloaded, download the newest compatible one
if (!images.compatibleImages.isEmpty()) {
@@ -275,25 +351,23 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device,
<< versionToDownload;
// Connect a one-time slot to mount the image after download finishes
// connect(
// this, &DevDiskManager::imageDownloadFinished, this,
// [this, device, downloadPath](const QString &finishedVersion,
// bool success,
// const QString &errorMessage) {
// if (success && finishedVersion == versionToDownload) {
// qDebug() << "Download finished for" << finishedVersion
// << ". Now attempting to mount.";
// mountImage(finishedVersion, device->udid.c_str(),
// downloadPath);
// // TODO: You might want to emit another signal here to
// // notify the UI of the final mount result.
// } else if (!success) {
// qDebug() << "Failed to download" << finishedVersion <<
// ":"
// << errorMessage;
// }
// },
// Qt::SingleShotConnection);
connect(
this, &DevDiskManager::imageDownloadFinished, this,
[this, device, downloadPath,
versionToDownload](const QString &finishedVersion, bool success,
const QString &errorMessage) {
if (success && finishedVersion == versionToDownload) {
qDebug() << "Download finished for" << finishedVersion
<< ". Now attempting to mount.";
mountImage(finishedVersion, device->udid.c_str());
// TODO: You might want to emit another signal here to
// notify the UI of the final mount result.
} else if (!success) {
qDebug() << "Failed to download" << finishedVersion << ":"
<< errorMessage;
}
},
Qt::SingleShotConnection);
// Start the download
QPair<QNetworkReply *, QNetworkReply *> replies =
@@ -324,21 +398,20 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device,
return false;
}
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device,
const QString &downloadPath)
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
{
if (m_isImageListReady) {
// If the list is already fetched, run the logic immediately.
return mountCompatibleImageInternal(device, downloadPath);
return mountCompatibleImageInternal(device);
} else {
// Otherwise, connect to the signal and wait.
qDebug() << "Image list not ready, waiting for it to be fetched...";
connect(
this, &DevDiskManager::imageListFetched, this,
[this, device, downloadPath](bool success) {
[this, device](bool success) {
if (success) {
qDebug() << "Image list is now ready. Retrying mount...";
mountCompatibleImageInternal(device, downloadPath);
mountCompatibleImageInternal(device);
} else {
qDebug() << "Failed to fetch image list. Cannot mount.";
}
@@ -351,9 +424,10 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device,
}
}
bool DevDiskManager::mountImage(const QString &version, const QString &udid,
const QString &downloadPath)
bool DevDiskManager::mountImage(const QString &version, const QString &udid)
{
const QString downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
if (!isImageDownloaded(version, downloadPath)) {
return false;
}
+10 -14
View File
@@ -19,12 +19,10 @@ public:
// TODO:public or private?
// Image list management
QNetworkReply *fetchImageList();
GetImagesSortedFinalResult parseImageList(const QByteArray &jsonData,
const QString &downloadPath,
int deviceMajorVersion = 0,
int deviceMinorVersion = 0,
const char *mounted_sig = nullptr,
uint64_t mounted_sig_len = 0);
GetImagesSortedFinalResult parseImageList(int deviceMajorVersion,
int deviceMinorVersion,
const char *mounted_sig,
uint64_t mounted_sig_len);
QList<ImageInfo> getAllImages() const;
// Download management
@@ -35,8 +33,7 @@ public:
// Mount operations
bool mountImage(const QString &version, const QString &udid,
const QString &downloadPath);
bool mountImage(const QString &version, const QString &udid);
bool unmountImage();
// Signature comparison
@@ -45,8 +42,8 @@ public:
QByteArray getImageListData() const { return m_imageListJsonData; }
GetMountedImageResult getMountedImage(const char *udid);
bool mountCompatibleImage(iDescriptorDevice *device,
const QString &downloadPath);
bool mountCompatibleImage(iDescriptorDevice *device);
bool downloadCompatibleImage(iDescriptorDevice *device);
signals:
void imageListFetched(bool success,
@@ -83,10 +80,9 @@ private:
GetImagesSortedResult
getImagesSorted(QMap<QString, QMap<QString, QString>> imageFiles,
int deviceMajorVersion, int deviceMinorVersion,
const QString &downloadPath, const char *mounted_sig,
uint64_t mounted_sig_len);
bool mountCompatibleImageInternal(iDescriptorDevice *device,
const QString &downloadPath);
const char *mounted_sig, uint64_t mounted_sig_len);
bool mountCompatibleImageInternal(iDescriptorDevice *device);
bool downloadCompatibleImageInternal(iDescriptorDevice *device);
};
#endif // DEVDISKMANAGER_H
+216
View File
@@ -0,0 +1,216 @@
#include "devicemanagerwidget.h"
#include "appcontext.h"
#include "devicemenuwidget.h"
#include <QDebug>
DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
: QWidget(parent), m_currentDeviceUuid("")
{
setupUI();
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
[this](iDescriptorDevice *device) {
addDevice(device);
setCurrentDevice(device->udid);
emit updateNoDevicesConnected();
});
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
[this](const std::string &uuid) {
removeDevice(uuid);
emit updateNoDevicesConnected();
});
// TODO: doesnt seem to work
// connect(
// AppContext::sharedInstance(), &AppContext::devicePairPending, this,
// [this](const QString &udid) {
// QWidget *placeholderWidget = new QWidget();
// QVBoxLayout *layout = new QVBoxLayout(placeholderWidget);
// QLabel *label = new QLabel(
// "Device is not paired. Please pair the device to continue.");
// label->setAlignment(Qt::AlignCenter);
// layout->addWidget(label);
// placeholderWidget->setLayout(layout);
// m_device_menu_widgets[udid.toStdString()] = placeholderWidget;
// QString tabTitle = QString::fromStdString(udid.toStdString());
// int mostRecentDevice =
// m_deviceManager->addDevice(placeholderWidget, tabTitle);
// m_deviceManager->setCurrentDevice(mostRecentDevice);
// ui->stackedWidget->setCurrentIndex(1); // Show device list page
// });
// TODO: could use some refactoring
// connect(AppContext::sharedInstance(), &AppContext::devicePaired, this,
// [this](iDescriptorDevice *device) {
// qDebug() << "Device paired:"
// << QString::fromStdString(device->udid);
// DeviceMenuWidget *deviceWidget = new
// DeviceMenuWidget(device);
// QString tabTitle =
// QString::fromStdString(device->deviceInfo.productType);
// int mostRecentDevice =
// addDevice(deviceWidget, tabTitle);
// setCurrentDevice(mostRecentDevice);
// // Makes sense ?
// // emit updateNoDevicesConnected()
// // Clean up old mapping and update
// if (m_device_menu_widgets.count(device->udid)) {
// m_device_menu_widgets[device->udid]->deleteLater();
// }
// m_device_menu_widgets[device->udid] = deviceWidget;
// });
// connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved,
// this, [this](const QString &ecid) {
// qDebug() << "Removing:" << ecid;
// std::string ecidStr = ecid.toStdString();
// DeviceMenuWidget *deviceWidget =
// qobject_cast<DeviceMenuWidget *>(
// m_device_menu_widgets[ecidStr]);
// if (deviceWidget) {
// // TODO: Implement proper removal by device index
// m_device_menu_widgets.erase(ecidStr);
// delete deviceWidget;
// }
// emit updateNoDevicesConnected();
// });
}
void DeviceManagerWidget::setupUI()
{
m_mainLayout = new QHBoxLayout(this);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->setSpacing(0);
// Create sidebar
m_sidebar = new DeviceSidebarWidget();
// Create stacked widget for device content
m_stackedWidget = new QStackedWidget();
// Add to layout
m_mainLayout->addWidget(m_sidebar);
m_mainLayout->addWidget(m_stackedWidget,
1); // Give stacked widget more space
// Connect signals
connect(m_sidebar, &DeviceSidebarWidget::sidebarDeviceChanged, this,
&DeviceManagerWidget::onSidebarDeviceChanged);
connect(m_sidebar, &DeviceSidebarWidget::sidebarNavigationChanged, this,
&DeviceManagerWidget::onSidebarNavigationChanged);
}
int DeviceManagerWidget::addDevice(iDescriptorDevice *device)
{
qDebug() << "Connect ::deviceAdded Adding:"
<< QString::fromStdString(device->udid);
DeviceMenuWidget *deviceWidget = new DeviceMenuWidget(device, this);
QString tabTitle = QString::fromStdString(device->deviceInfo.productType);
int deviceIndex = m_stackedWidget->addWidget(deviceWidget);
m_deviceWidgets[device->udid] = std::pair{
deviceWidget, m_sidebar->addToSidebar(tabTitle, device->udid)};
// If this is the first device, make it current
// if (m_currentDeviceIndex == -1) {
// setCurrentDevice(deviceIndex);
// }
return deviceIndex;
}
void DeviceManagerWidget::removeDevice(const std::string &uuid)
{
qDebug() << "Removing:" << QString::fromStdString(uuid);
std::pair<DeviceMenuWidget *, DeviceSidebarItem *> &d =
m_deviceWidgets[uuid];
if (d.first != nullptr && d.second != nullptr) {
// TODO: cleanups
m_deviceWidgets.remove(uuid);
delete d.first;
delete d.second;
if (m_deviceWidgets.count() > 0) {
setCurrentDevice(m_deviceWidgets.firstKey());
m_sidebar->updateSidebar(m_deviceWidgets.firstKey());
}
}
}
void DeviceManagerWidget::setCurrentDevice(const std::string &uuid)
{
qDebug() << "Setting current device to:" << QString::fromStdString(uuid);
if (m_currentDeviceUuid == uuid)
return;
if (!m_deviceWidgets.contains(uuid)) {
qWarning() << "Device UUID not found:" << QString::fromStdString(uuid);
return;
}
// m_currentDeviceIndex = deviceIndex;
m_currentDeviceUuid = uuid;
// // Update sidebar selection
// m_sidebar->setCurrentDevice(deviceIndex);
// // Update stacked widget
QWidget *widget = m_deviceWidgets[uuid].first;
m_stackedWidget->setCurrentWidget(widget);
emit deviceChanged(uuid);
}
std::string DeviceManagerWidget::getCurrentDevice() const
{
return m_currentDeviceUuid;
}
QWidget *DeviceManagerWidget::getDeviceWidget(int deviceIndex) const
{
// return m_deviceWidgets.value(deviceIndex, nullptr);
}
void DeviceManagerWidget::setDeviceNavigation(int deviceIndex,
const QString &section)
{
m_sidebar->setDeviceNavigationSection(deviceIndex, section);
// emit deviceNavigationChanged(deviceIndex, section);
}
void DeviceManagerWidget::onSidebarDeviceChanged(std::string deviceUuid)
{
setCurrentDevice(deviceUuid);
}
void DeviceManagerWidget::onSidebarNavigationChanged(std::string deviceUuid,
const QString &section)
{
if (deviceUuid != m_currentDeviceUuid) {
setCurrentDevice(deviceUuid);
}
QWidget *tabWidget = m_deviceWidgets[deviceUuid].first;
DeviceMenuWidget *deviceMenuWidget =
qobject_cast<DeviceMenuWidget *>(tabWidget);
if (deviceMenuWidget) {
// Call a method to change the internal tab
deviceMenuWidget->switchToTab(section);
}
// if (deviceIndex != m_currentDeviceIndex) {
// setCurrentDevice(deviceIndex);
// }
// emit sidebarNavigationChanged(deviceUuid, section);
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef DEVICEMANAGERWIDGET_H
#define DEVICEMANAGERWIDGET_H
#include "devicemenuwidget.h"
#include "devicesidebarwidget.h"
#include "iDescriptor.h"
#include <QHBoxLayout>
#include <QMap>
#include <QStackedWidget>
#include <QWidget>
class DeviceManagerWidget : public QWidget
{
Q_OBJECT
public:
explicit DeviceManagerWidget(QWidget *parent = nullptr);
int addDevice(iDescriptorDevice *device);
void removeDevice(const std::string &uuid);
void setCurrentDevice(const std::string &uuid);
std::string getCurrentDevice() const;
// Get the device widget at a specific index
QWidget *getDeviceWidget(int deviceIndex) const;
// Navigation methods
void setDeviceNavigation(int deviceIndex, const QString &section);
signals:
void deviceChanged(std::string deviceUuid);
void sidebarNavigationChanged(std::string deviceUuid,
const QString &section);
void updateNoDevicesConnected();
private slots:
void onSidebarDeviceChanged(std::string deviceUuid);
void onSidebarNavigationChanged(std::string deviceUuid,
const QString &section);
private:
void setupUI();
QHBoxLayout *m_mainLayout;
DeviceSidebarWidget *m_sidebar;
QStackedWidget *m_stackedWidget;
QMap<std::string, std::pair<DeviceMenuWidget *, DeviceSidebarItem *>>
m_deviceWidgets; // Map to store devices by UDID
std::string m_currentDeviceUuid;
};
#endif // DEVICEMANAGERWIDGET_H
+331
View File
@@ -0,0 +1,331 @@
#include "devicesidebarwidget.h"
#include "clickablewidget.h"
#include <QDebug>
#include <QEasingCurve>
// DeviceSidebarItem Implementation
DeviceSidebarItem::DeviceSidebarItem(const QString &deviceName,
const std::string &uuid, QWidget *parent)
: QFrame(parent), m_deviceName(deviceName), m_uuid(uuid), m_selected(false),
m_collapsed(true)
{
setupUI();
setFrameStyle(QFrame::StyledPanel);
setLineWidth(1);
updateToggleButton();
// Initialize animation
m_collapseAnimation =
new QPropertyAnimation(m_optionsWidget, "maximumHeight", this);
m_collapseAnimation->setDuration(200);
m_collapseAnimation->setEasingCurve(QEasingCurve::InOutQuad);
}
void DeviceSidebarItem::setupUI()
{
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(5, 5, 5, 5);
m_mainLayout->setSpacing(5);
// Header section (always visible)
m_headerWidget = new ClickableWidget();
QVBoxLayout *headerLayout = new QVBoxLayout(m_headerWidget);
headerLayout->setContentsMargins(0, 0, 0, 0);
headerLayout->setSpacing(2);
m_headerWidget->setStyleSheet("ClickableWidget { }");
connect(m_headerWidget, &ClickableWidget::clicked, this,
[this]() { emit deviceSelected(m_uuid); });
// Device name label
m_deviceLabel = new QLabel(m_deviceName);
m_deviceLabel->setStyleSheet("QLabel { font-weight: bold; color: #333; }");
m_deviceLabel->setWordWrap(true);
headerLayout->addWidget(m_deviceLabel);
// Toggle button
m_toggleButton = new QPushButton();
m_toggleButton->setFlat(true);
m_toggleButton->setMaximumHeight(20);
m_toggleButton->setStyleSheet("QPushButton { "
" text-align: left; "
" padding: 2px 5px; "
" border: none; "
" color: #666; "
" font-size: 11px; "
"} "
"QPushButton:hover { "
" background-color: #f0f0f0; "
" border-radius: 3px; "
"}");
connect(m_toggleButton, &QPushButton::clicked, this,
&DeviceSidebarItem::onToggleCollapse);
headerLayout->addWidget(m_toggleButton);
m_mainLayout->addWidget(m_headerWidget);
// Options section (collapsible)
m_optionsWidget = new QWidget();
QVBoxLayout *optionsLayout = new QVBoxLayout(m_optionsWidget);
optionsLayout->setContentsMargins(5, 5, 5, 5);
optionsLayout->setSpacing(3);
// Create navigation buttons
m_infoButton = new QPushButton("Info");
m_appsButton = new QPushButton("Apps");
m_galleryButton = new QPushButton("Gallery");
m_filesButton = new QPushButton("Files");
// Create button group for exclusive selection
m_navigationGroup = new QButtonGroup(this);
m_navigationGroup->addButton(m_infoButton, 0);
m_navigationGroup->addButton(m_appsButton, 1);
m_navigationGroup->addButton(m_galleryButton, 2);
m_navigationGroup->addButton(m_filesButton, 3);
// Style the navigation buttons
QList<QPushButton *> navButtons = {m_infoButton, m_appsButton,
m_galleryButton, m_filesButton};
for (QPushButton *btn : navButtons) {
btn->setCheckable(true);
btn->setMaximumHeight(25);
btn->setStyleSheet("QPushButton { "
" background-color: #f8f9fa; "
" border: 1px solid #dee2e6; "
" padding: 4px 8px; "
" text-align: center; "
" border-radius: 3px; "
" font-size: 11px; "
"} "
"QPushButton:checked { "
" background-color: #0d6efd; "
" color: white; "
" border: 1px solid #0a58ca; "
"} "
"QPushButton:hover:!checked { "
" background-color: #e9ecef; "
" border-color: #adb5bd; "
"} "
"QPushButton:checked:hover { "
" background-color: #0b5ed7; "
"}");
connect(btn, &QPushButton::clicked, this,
&DeviceSidebarItem::onNavigationButtonClicked);
optionsLayout->addWidget(btn);
}
// Set default selection
m_infoButton->setChecked(true);
m_mainLayout->addWidget(m_optionsWidget);
// Initially hide options
m_optionsWidget->setMaximumHeight(0);
m_optionsWidget->hide();
setStyleSheet("DeviceSidebarItem { border: "
"1px solid #e0e0e0; border-radius: 5px; }");
}
void DeviceSidebarItem::setSelected(bool selected)
{
if (m_selected == selected)
return;
m_selected = selected;
if (selected) {
setStyleSheet("DeviceSidebarItem { border: "
"2px solid #2196f3; border-radius: 5px; }");
} else {
setStyleSheet("DeviceSidebarItem { border: "
"1px solid #e0e0e0; border-radius: 5px; }");
}
}
void DeviceSidebarItem::setCollapsed(bool collapsed)
{
if (m_collapsed == collapsed)
return;
m_collapsed = collapsed;
updateToggleButton();
animateCollapse();
}
void DeviceSidebarItem::updateToggleButton()
{
if (m_collapsed) {
m_toggleButton->setText("▶ Options");
} else {
m_toggleButton->setText("▼ Options");
}
}
void DeviceSidebarItem::animateCollapse()
{
m_collapseAnimation->stop();
if (m_collapsed) {
// Collapsing
m_collapseAnimation->setStartValue(m_optionsWidget->height());
m_collapseAnimation->setEndValue(0);
connect(m_collapseAnimation, &QPropertyAnimation::finished, this,
[this]() {
m_optionsWidget->hide();
disconnect(m_collapseAnimation,
&QPropertyAnimation::finished, this, nullptr);
});
} else {
// Expanding
m_optionsWidget->show();
m_optionsWidget->setMaximumHeight(QWIDGETSIZE_MAX);
int targetHeight = m_optionsWidget->sizeHint().height();
m_optionsWidget->setMaximumHeight(0);
m_collapseAnimation->setStartValue(0);
m_collapseAnimation->setEndValue(targetHeight);
connect(m_collapseAnimation, &QPropertyAnimation::finished, this,
[this]() {
m_optionsWidget->setMaximumHeight(QWIDGETSIZE_MAX);
disconnect(m_collapseAnimation,
&QPropertyAnimation::finished, this, nullptr);
});
}
m_collapseAnimation->start();
}
void DeviceSidebarItem::onToggleCollapse() { setCollapsed(!m_collapsed); }
void DeviceSidebarItem::onNavigationButtonClicked()
{
QPushButton *button = qobject_cast<QPushButton *>(sender());
if (button) {
emit navigationRequested(m_uuid, button->text());
emit deviceSelected(m_uuid);
}
}
const std::string &DeviceSidebarItem::getDeviceUuid() const { return m_uuid; }
// DeviceSidebarWidget Implementation
DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) : QWidget(parent)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Create scroll area
m_scrollArea = new QScrollArea();
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_scrollArea->setFrameStyle(QFrame::NoFrame);
// Create content widget
m_contentWidget = new QWidget();
m_contentLayout = new QVBoxLayout(m_contentWidget);
m_contentLayout->setContentsMargins(5, 5, 5, 5);
m_contentLayout->setSpacing(10);
m_contentLayout->addStretch(); // Push items to top
m_scrollArea->setWidget(m_contentWidget);
mainLayout->addWidget(m_scrollArea);
// Set minimum width
setMinimumWidth(200);
setMaximumWidth(250);
}
DeviceSidebarItem *DeviceSidebarWidget::addToSidebar(const QString &deviceName,
const std::string &uuid)
{
DeviceSidebarItem *item = new DeviceSidebarItem(deviceName, uuid, this);
connect(item, &DeviceSidebarItem::deviceSelected, this,
&DeviceSidebarWidget::onDeviceSelected);
connect(item, &DeviceSidebarItem::navigationRequested, this,
&DeviceSidebarWidget::onSidebarNavigationChanged);
// TODO
m_currentDeviceUuid = uuid;
// item->setSelected(true);
m_deviceSidebarItems.append(item);
updateSelection();
// m_deviceItems.append(item);
m_contentLayout->insertWidget(m_contentLayout->count() - 1,
item); // Insert before stretch
// Auto-select first device
// if (m_currentDeviceIndex == -1) {
// setCurrentDevice(deviceIndex);
// }
return item;
}
void DeviceSidebarWidget::setCurrentDevice(std::string uuid)
{
if (m_currentDeviceUuid == uuid)
return;
m_currentDeviceUuid = uuid;
updateSelection();
emit sidebarDeviceChanged(uuid);
}
void DeviceSidebarWidget::setDeviceNavigationSection(int deviceIndex,
const QString &section)
{
// for (DeviceSidebarItem *item : m_deviceItems) {
// if (item->getDeviceIndex() == deviceIndex) {
// // Find and check the appropriate button
// QPushButton *targetButton = nullptr;
// if (section == "Info")
// targetButton = item->findChild<QPushButton *>();
// else if (section == "Apps")
// targetButton = item->findChildren<QPushButton *>().value(1);
// else if (section == "Gallery")
// targetButton = item->findChildren<QPushButton *>().value(2);
// else if (section == "Files")
// targetButton = item->findChildren<QPushButton *>().value(3);
// if (targetButton) {
// targetButton->setChecked(true);
// }
// break;
// }
// }
}
void DeviceSidebarWidget::onDeviceSelected(std::string uuid)
{
setCurrentDevice(uuid);
}
void DeviceSidebarWidget::onSidebarNavigationChanged(std::string uuid,
const QString &section)
{
if (uuid != m_currentDeviceUuid) {
setCurrentDevice(uuid);
}
emit sidebarNavigationChanged(uuid, section);
}
void DeviceSidebarWidget::updateSidebar(std::string uuid)
{
// TODO : need a proper check
if (m_deviceSidebarItems.isEmpty())
return;
m_currentDeviceUuid = uuid;
updateSelection();
}
void DeviceSidebarWidget::updateSelection()
{
for (DeviceSidebarItem *item : m_deviceSidebarItems) {
item->setSelected(item->getDeviceUuid() == m_currentDeviceUuid);
}
}
+98
View File
@@ -0,0 +1,98 @@
#ifndef DEVICESIDEBARWIDGET_H
#define DEVICESIDEBARWIDGET_H
#include "clickablewidget.h"
#include <QButtonGroup>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class DeviceSidebarItem : public QFrame
{
Q_OBJECT
public:
explicit DeviceSidebarItem(const QString &deviceName,
const std::string &uuid,
QWidget *parent = nullptr);
const std::string &getDeviceUuid() const;
void setSelected(bool selected);
bool isSelected() const { return m_selected; }
void setCollapsed(bool collapsed);
bool isCollapsed() const { return m_collapsed; }
signals:
void deviceSelected(const std::string &uuid);
void navigationRequested(const std::string &uuid, const QString &section);
private slots:
void onToggleCollapse();
void onNavigationButtonClicked();
private:
void setupUI();
void updateToggleButton();
void animateCollapse();
std::string m_uuid;
QString m_deviceName;
bool m_selected;
bool m_collapsed;
QVBoxLayout *m_mainLayout;
ClickableWidget *m_headerWidget;
QWidget *m_optionsWidget;
QPushButton *m_toggleButton;
QLabel *m_deviceLabel;
// Navigation buttons
QPushButton *m_infoButton;
QPushButton *m_appsButton;
QPushButton *m_galleryButton;
QPushButton *m_filesButton;
QButtonGroup *m_navigationGroup;
QPropertyAnimation *m_collapseAnimation;
};
class DeviceSidebarWidget : public QWidget
{
Q_OBJECT
public:
explicit DeviceSidebarWidget(QWidget *parent = nullptr);
std::string getUuid() const;
DeviceSidebarItem *addToSidebar(const QString &deviceName,
const std::string &uuid);
void setDeviceNavigationSection(int deviceIndex, const QString &section);
void updateSidebar(std::string uuid);
public slots:
void onSidebarNavigationChanged(std::string uuid, const QString &section);
signals:
void sidebarNavigationChanged(std::string uuid, const QString &section);
void deviceNavigationChanged(std::string uuid, const QString &section);
void sidebarDeviceChanged(std::string uuid);
private:
void updateSelection();
void onDeviceSelected(std::string uuid);
void setCurrentDevice(std::string uuid);
QScrollArea *m_scrollArea;
QWidget *m_contentWidget;
QVBoxLayout *m_contentLayout;
std::string m_currentDeviceUuid;
QList<DeviceSidebarItem *> m_deviceSidebarItems;
};
#endif // DEVICESIDEBARWIDGET_H
-169
View File
@@ -1,169 +0,0 @@
#include "devicetabwidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFrame>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QSpinBox>
#include <QStyle>
#include <QTimer>
#include <QVBoxLayout>
#include <QWheelEvent>
DeviceTabWidget::DeviceTabWidget(QWidget *parent) : QTabWidget(parent)
{
setTabsClosable(false);
setTabPosition(QTabWidget::West); // Set tabs to appear on the left side
connect(this, &QTabWidget::tabCloseRequested, this,
&DeviceTabWidget::onCloseTab);
}
int DeviceTabWidget::addTabCustom(QWidget *widget, const QString &text)
{
int index = addTab(widget, text);
QWidget *tabWidget = createTabWidget(text, index);
// tabWidget->setMinimumHeight(220); // Set a minimum height for the tab
// widget tabWidget->setSizePolicy(QSizePolicy::Expanding,
// QSizePolicy::Expanding);
// tabWidget->setSizePolicy(QSizePolicy::Expanding,
// QSizePolicy::Preferred);
tabBar()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
tabBar()->setTabButton(index, QTabBar::LeftSide, tabWidget);
tabBar()->setTabText(index, ""); // Clear the default text
return index;
}
void DeviceTabWidget::wheelEvent(QWheelEvent *event)
{
// Ignore wheel events to prevent tab switching with scroll wheel
event->ignore();
}
void DeviceTabWidget::setTabIcon(int index, const QPixmap &icon)
{
if (index >= 0 && index < count()) {
QString text = tabBar()->tabText(index);
if (text.isEmpty()) {
// Get text from the custom widget if it exists
QWidget *tabWidget = tabBar()->tabButton(index, QTabBar::LeftSide);
if (tabWidget) {
QLabel *textLabel = tabWidget->findChild<QLabel *>("textLabel");
if (textLabel) {
text = textLabel->text();
}
}
}
QWidget *newTabWidget = createTabWidget(text, index);
tabBar()->setTabButton(index, QTabBar::LeftSide, newTabWidget);
}
}
QWidget *DeviceTabWidget::createTabWidget(const QString &text, int index)
{
QWidget *tabWidget = new QWidget();
// tabWidget->setMinimumHeight(220); // Set a minimum height for the tab
// widget
tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
tabWidget->setStyleSheet("QWidget { "
"}");
QVBoxLayout *mainLayout = new QVBoxLayout(tabWidget);
mainLayout->setContentsMargins(5, 2, 5, 2);
mainLayout->setSpacing(2);
mainLayout->setSizeConstraint(QLayout::SetMinimumSize);
// Top section with icon and text
QWidget *topSection = new QWidget();
QHBoxLayout *topLayout = new QHBoxLayout(topSection);
topLayout->setContentsMargins(0, 0, 0, 0);
topLayout->setSpacing(5);
// Add text
QLabel *textLabel = new QLabel(text);
textLabel->setObjectName("textLabel");
topLayout->addWidget(textLabel);
mainLayout->addWidget(topSection);
// Create collapsible options section
QPushButton *toggleButton = new QPushButton("▶ Options");
toggleButton->setFlat(true);
toggleButton->setStyleSheet(
"QPushButton { text-align: left; padding: 2px; }");
toggleButton->setMinimumHeight(20);
toggleButton->setMaximumHeight(25);
toggleButton->setCheckable(true);
toggleButton->setChecked(false);
QWidget *contentWidget = new QWidget();
contentWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
QVBoxLayout *optionsLayout = new QVBoxLayout(contentWidget);
optionsLayout->setContentsMargins(5, 5, 5, 5);
optionsLayout->setSpacing(3);
// Create navigation buttons
QPushButton *infoBtn = new QPushButton("Info");
QPushButton *appsBtn = new QPushButton("Apps");
QPushButton *galleryBtn = new QPushButton("Gallery");
QPushButton *filesBtn = new QPushButton("Files");
// Set button properties
QList<QPushButton *> buttons = {infoBtn, appsBtn, galleryBtn, filesBtn};
for (QPushButton *btn : buttons) {
btn->setMaximumHeight(25);
btn->setCheckable(true);
btn->setStyleSheet("QPushButton { "
" background-color: #f0f0f0; "
" border: 1px solid #ccc; "
" padding: 4px 8px; "
" text-align: center; "
"} "
"QPushButton:checked { "
" background-color: #0078d4; "
" color: white; "
" border: 1px solid #005a9e; "
"} "
"QPushButton:hover { "
" background-color: #e5e5e5; "
"} "
"QPushButton:checked:hover { "
" background-color: #106ebe; "
"}");
}
// Set info as default active
infoBtn->setChecked(true);
// Connect button group behavior and emit signals
for (QPushButton *btn : buttons) {
connect(btn, &QPushButton::clicked, [this, buttons, btn, index]() {
for (QPushButton *otherBtn : buttons) {
if (otherBtn != btn) {
otherBtn->setChecked(false);
}
}
btn->setChecked(true);
emit navigationButtonClicked(index, btn->text());
});
}
// Add buttons to layout
optionsLayout->addWidget(infoBtn);
optionsLayout->addWidget(appsBtn);
optionsLayout->addWidget(galleryBtn);
optionsLayout->addWidget(filesBtn);
// Set the content widget in the scroll area
// Add widgets to main layout
mainLayout->addWidget(toggleButton);
mainLayout->addWidget(contentWidget);
contentWidget->setVisible(false); // Initially hidden
// Connect toggle button to expand/collapse
int prevHeight = tabBar()->sizeHint().height();
connect(toggleButton, &QPushButton::clicked,
[this, toggleButton, contentWidget, prevHeight]() {
// Toggle content visibility
bool isExpanded = toggleButton->isChecked();
contentWidget->setVisible(isExpanded);
if (isExpanded) {
// Expanding
toggleButton->setText("▼ Options");
tabBar()->resize(tabBar()->sizeHint().width(),
contentWidget->sizeHint().height() +
tabBar()->sizeHint().height());
tabBar()->adjustSize();
} else {
// Collapsing
toggleButton->setText("▶ Options");
tabBar()->setFixedHeight(prevHeight);
}
// QTimer::singleShot(0, tabBar(), &QWidget::adjustSize);
});
return tabWidget;
}
void DeviceTabWidget::onCloseTab(int index) { removeTab(index); }
-31
View File
@@ -1,31 +0,0 @@
#ifndef DEVICETABWIDGET_H
#define DEVICETABWIDGET_H
#include <QLabel>
#include <QPushButton>
#include <QTabWidget>
class DeviceTabWidget : public QTabWidget
{
Q_OBJECT
public:
explicit DeviceTabWidget(QWidget *parent = nullptr);
int addTabCustom(QWidget *widget, const QString &text);
void setTabIcon(int index, const QPixmap &icon);
signals:
void navigationButtonClicked(int tabIndex, const QString &buttonText);
private slots:
void onCloseTab(int index);
private:
QWidget *createTabWidget(const QString &text, int index);
protected:
void wheelEvent(QWheelEvent *event) override;
};
#endif // DEVICETABWIDGET_H
+5
View File
@@ -5,6 +5,11 @@
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QCoreApplication::setOrganizationName("iDescriptor");
// QCoreApplication::setOrganizationDomain("iDescriptor.com");
QCoreApplication::setApplicationName("iDescriptor");
MainWindow w;
w.show();
return a.exec();
+33 -207
View File
@@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "detailwindow.h"
#include "settingswidget.h"
#include <QDialog>
#include <QGraphicsScene>
#include <QGraphicsSvgItem>
@@ -13,7 +14,7 @@
#include <unistd.h>
#include "appswidget.h"
#include "devicetabwidget.h"
#include "devicemanagerwidget.h"
#include "iDescriptor.h"
#include "libirecovery.h"
#include "toolboxwidget.h"
@@ -116,35 +117,36 @@ MainWindow::MainWindow(QWidget *parent)
{
ui->setupUi(this);
// Replace the default tab widget with custom one
DeviceTabWidget *customTabWidget = new DeviceTabWidget(this);
m_deviceManager = new DeviceManagerWidget(this);
ui->stackedWidget->insertWidget(1, m_deviceManager);
connect(m_deviceManager, &DeviceManagerWidget::updateNoDevicesConnected,
this, &MainWindow::updateNoDevicesConnected);
// Replace the existing tabWidget in the UI
QWidget *tabWidgetParent = ui->tabWidget->parentWidget();
QLayout *parentLayout = tabWidgetParent->layout();
if (parentLayout) {
parentLayout->replaceWidget(ui->tabWidget, customTabWidget);
}
delete ui->tabWidget;
ui->tabWidget = customTabWidget;
// settings button
QPushButton *settingsButton = new QPushButton();
settingsButton->setIcon(QIcon::fromTheme("settings"));
settingsButton->setToolTip("Settings");
settingsButton->setFlat(true);
settingsButton->setCursor(Qt::PointingHandCursor);
settingsButton->setFixedSize(24, 24);
connect(settingsButton, &QPushButton::clicked, this, [this]() {
QDialog settingsDialog(this);
settingsDialog.setWindowTitle("Settings");
settingsDialog.setModal(true);
settingsDialog.resize(400, 300);
QVBoxLayout *layout = new QVBoxLayout(&settingsDialog);
SettingsWidget *settingsWidget = new SettingsWidget(&settingsDialog);
layout->addWidget(settingsWidget);
settingsDialog.setLayout(layout);
settingsDialog.exec();
});
ui->centralwidget->layout()->addWidget(settingsButton);
ui->mainTabWidget->widget(1)->layout()->addWidget(new AppsWidget(this));
ui->mainTabWidget->widget(2)->layout()->addWidget(new ToolboxWidget(this));
ui->mainTabWidget->widget(3)->layout()->addWidget(
new JailbrokenWidget(this));
customTabWidget->tabBar()->setMinimumWidth(75);
customTabWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
customTabWidget->setStyleSheet("QTabWidget::pane {"
// " border: 1px solid #ccc;"
"}"
"QTabBar::tab {"
" padding: 5px;"
"}");
// customTabWidget->tabBar()->setMinimumHeight(100);
irecv_error_t res_recovery =
irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr);
@@ -156,155 +158,6 @@ MainWindow::MainWindow(QWidget *parent)
if (res != IDEVICE_E_SUCCESS) {
printf("ERROR: Unable to subscribe to device events.\n");
}
connect(
AppContext::sharedInstance(), &AppContext::deviceAdded, this,
[this](iDescriptorDevice *device) {
qDebug() << "Connect ::deviceAdded Adding:"
<< QString::fromStdString(device->udid);
// Create device info widget
DeviceMenuWidget *deviceWidget = new DeviceMenuWidget(device, this);
m_device_menu_widgets[device->udid] = deviceWidget;
// Get device icon and product type for tab
QString tabTitle =
QString::fromStdString(device->deviceInfo.productType);
// Add tab with custom icon
DeviceTabWidget *customTabWidget =
qobject_cast<DeviceTabWidget *>(ui->tabWidget);
int mostRecentDevice =
customTabWidget->addTabCustom(deviceWidget, tabTitle);
customTabWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
updateNoDevicesConnected();
connect(customTabWidget, &DeviceTabWidget::navigationButtonClicked,
this, [this](int tabIndex, const QString &buttonName) {
// Get the widget at the specified tab index
QWidget *tabWidget = ui->tabWidget->widget(tabIndex);
DeviceMenuWidget *deviceMenuWidget =
qobject_cast<DeviceMenuWidget *>(tabWidget);
if (deviceMenuWidget) {
// Call a method to change the internal tab
deviceMenuWidget->switchToTab(buttonName);
}
});
});
connect(
AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
[this](const std::string &uuid) {
qDebug() << "Removing:" << QString::fromStdString(uuid);
DeviceMenuWidget *deviceWidget =
qobject_cast<DeviceMenuWidget *>(m_device_menu_widgets[uuid]);
if (deviceWidget) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(deviceWidget));
m_device_menu_widgets.erase(uuid);
// deviceWidget->deleteLater();
delete deviceWidget;
}
updateNoDevicesConnected();
});
connect(
AppContext::sharedInstance(), &AppContext::devicePairPending, this,
[this](const QString &udid) {
QWidget *placeholderWidget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(placeholderWidget);
QLabel *label = new QLabel(
"Device is not paired. Please pair the device to continue.");
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
placeholderWidget->setLayout(layout);
m_device_menu_widgets[udid.toStdString()] = placeholderWidget;
// DeviceTabWidget *customTabWidget =
// qobject_cast<DeviceTabWidget *>(ui->tabWidget);
// QString tabTitle = QString::fromStdString(udid.toStdString());
// QPixmap placeholderIcon(16, 16);
// placeholderIcon.fill(Qt::red);
// int mostRecentDevice = customTabWidget->addTabWithIcon(
// placeholderWidget, placeholderIcon, tabTitle);
int mostRecentDevice = ui->tabWidget->addTab(
placeholderWidget, QIcon(),
QString::fromStdString(udid.toStdString()));
// customTabWidget->setSizePolicy(QSizePolicy::Expanding,
// QSizePolicy::Preferred);
// customTabWidget->setCurrentIndex(mostRecentDevice);
ui->tabWidget->setCurrentIndex(mostRecentDevice);
ui->stackedWidget->setCurrentIndex(1); // Show device list page
});
connect(AppContext::sharedInstance(), &AppContext::devicePaired, this,
[this](iDescriptorDevice *device) {
qDebug() << "Device paired:"
<< QString::fromStdString(device->udid);
DeviceMenuWidget *deviceWidget = new DeviceMenuWidget(device);
// Find the tab index for this device
int tabIndex = -1;
for (int i = 0; i < ui->tabWidget->count(); ++i) {
if (ui->tabWidget->tabText(i) ==
QString::fromStdString(device->udid)) {
tabIndex = i;
break;
}
}
// If tab exists, remove the old widget and tab
if (tabIndex != -1) {
QWidget *oldWidget = ui->tabWidget->widget(tabIndex);
ui->tabWidget->removeTab(tabIndex);
if (oldWidget)
oldWidget->deleteLater();
}
DeviceTabWidget *customTabWidget =
qobject_cast<DeviceTabWidget *>(ui->tabWidget);
QString tabTitle =
QString::fromStdString(device->deviceInfo.productType);
int mostRecentDevice =
customTabWidget->addTabCustom(deviceWidget, tabTitle);
// int mostRecentDevice = ui->tabWidget->addTab(
// placeholderWidget, getDeviceIcon(udid.toStdString()),
// QString::fromStdString(udid.toStdString()));
customTabWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
customTabWidget->setCurrentIndex(mostRecentDevice);
// ui->tabWidget->setCurrentIndex(mostRecentDevice);
ui->stackedWidget->setCurrentIndex(1); // Show device list page
// Clean up old mapping and update
if (m_device_menu_widgets.count(device->udid)) {
m_device_menu_widgets[device->udid]->deleteLater();
}
m_device_menu_widgets[device->udid] = deviceWidget;
});
connect(
AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved, this,
[this](const QString &ecid) {
qDebug() << "Removing:" << ecid;
std::string ecidStr = ecid.toStdString();
DeviceMenuWidget *deviceWidget = qobject_cast<DeviceMenuWidget *>(
m_device_menu_widgets[ecidStr]);
if (deviceWidget) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(deviceWidget));
m_device_menu_widgets.erase(ecidStr);
delete deviceWidget;
}
updateNoDevicesConnected();
});
}
void MainWindow::updateNoDevicesConnected()
@@ -355,34 +208,16 @@ void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj)
// customTabWidget->addTabWithIcon(recoveryDeviceInfoWidget,
// recoveryIcon, "Recovery Mode");
m_device_menu_widgets[added_ecid] = recoveryDeviceInfoWidget;
// m_device_menu_widgets[added_ecid] = recoveryDeviceInfoWidget;
// Get device icon and product type for tab
// QString tabTitle =
// QString::fromStdString(device->product.toStdString());
QString tabTitle = QString::fromStdString("recovery mode device");
// Add tab with custom icon
DeviceTabWidget *customTabWidget =
qobject_cast<DeviceTabWidget *>(ui->tabWidget);
int mostRecentDevice =
customTabWidget->addTabCustom(recoveryDeviceInfoWidget, tabTitle);
customTabWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
connect(customTabWidget, &DeviceTabWidget::navigationButtonClicked,
this, [this](int tabIndex, const QString &buttonName) {
// Get the widget at the specified tab index
QWidget *tabWidget = ui->tabWidget->widget(tabIndex);
DeviceMenuWidget *deviceMenuWidget =
qobject_cast<DeviceMenuWidget *>(tabWidget);
if (deviceMenuWidget) {
// Call a method to change the internal tab
deviceMenuWidget->switchToTab(buttonName);
}
});
ui->tabWidget->setCurrentIndex(mostRecentDevice);
// int mostRecentDevice =
// m_deviceManager->addDevice(recoveryDeviceInfoWidget, tabTitle);
// m_deviceManager->setCurrentDevice(mostRecentDevice);
} catch (const std::exception &e) {
qDebug() << "Exception in onDeviceAdded: " << e.what();
QMessageBox::critical(
@@ -398,19 +233,10 @@ void MainWindow::onRecoveryDeviceRemoved(QObject *deviceInfoObj)
return;
qDebug() << "Recovery device removed: " << info->ecid;
// Find the tab index for the recovery device
int tabIndex = -1;
for (int i = 0; i < ui->tabWidget->count(); ++i) {
if (ui->tabWidget->tabText(i) ==
QString::fromStdString("Recovery Mode Device")) {
tabIndex = i;
break;
}
}
if (tabIndex != -1) {
ui->tabWidget->removeTab(tabIndex);
qDebug() << "Removed tab for recovery device: " << info->ecid;
}
// TODO: Implement proper device removal in DeviceManagerWidget
// For now, we'll just log the removal
qDebug() << "Recovery device cleanup not yet implemented";
}
MainWindow::~MainWindow()
+5 -4
View File
@@ -1,5 +1,6 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "devicemanagerwidget.h"
#include "devicemenuwidget.h"
#include "iDescriptor.h"
#include "libirecovery.h"
@@ -24,17 +25,17 @@ public:
signals:
void deviceAdded(QString udid); // Signal for device connections
private slots:
public slots:
void onRecoveryDeviceAdded(
QObject *device_info); // Slot for recovery device connections
void onRecoveryDeviceRemoved(
QObject *device_info); // Slot for recovery device disconnections
void onDeviceInitFailed(QString udid, lockdownd_error_t err);
void updateNoDevicesConnected();
private:
std::map<std::string, QWidget *>
m_device_menu_widgets; // Map to store devices by UDID
void updateNoDevicesConnected();
Ui::MainWindow *ui;
DeviceManagerWidget *m_deviceManager; // Add this member
};
#endif // MAINWINDOW_H
+5 -22
View File
@@ -98,42 +98,25 @@
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="devicePageLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>
</attribute>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Tab 2</string>
</attribute>
</widget>
</widget>
</item>
</layout>
<layout class="QVBoxLayout" name="devicePageLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="appsTab">
<widget class="QWidget" name="appsTab">
<attribute name="title">
<string>Apps</string>
</attribute>
<layout class="QVBoxLayout" name="appsTabLayout">
</layout>
<layout class="QVBoxLayout" name="appsTabLayout"/>
</widget>
<widget class="QWidget" name="toolboxTab">
<attribute name="title">
<string>Toolbox</string>
</attribute>
<layout class="QVBoxLayout" name="toolboxTabLayout">
</layout>
<layout class="QVBoxLayout" name="toolboxTabLayout"/>
</widget>
<widget class="QWidget" name="jailbrokenTab">
<widget class="QWidget" name="jailbrokenTab">
<attribute name="title">
<string>Jailbroken</string>
</attribute>
+22
View File
@@ -0,0 +1,22 @@
#include "settingsmanager.h"
#include <QSettings>
#define DEFAULT_DEVDISKIMGPATH "./devdiskimages"
SettingsManager *SettingsManager::sharedInstance()
{
static SettingsManager instance;
return &instance;
}
SettingsManager::SettingsManager(QObject *parent) : QObject{parent}
{
m_settings = new QSettings(this);
}
QString SettingsManager::devdiskimgpath() const
{
return m_settings->value("devdiskimgpath", DEFAULT_DEVDISKIMGPATH)
.toString();
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef SETTINGSMANAGER_H
#define SETTINGSMANAGER_H
#include <QObject>
#include <QSettings>
class SettingsManager : public QObject
{
Q_OBJECT
public:
explicit SettingsManager(QObject *parent = nullptr);
static SettingsManager *sharedInstance();
signals:
public slots:
QString devdiskimgpath() const;
private:
QSettings *m_settings;
};
#endif // SETTINGSMANAGER_H
+320
View File
@@ -0,0 +1,320 @@
#include "settingswidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFileDialog>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QSpinBox>
#include <QStandardPaths>
#include <QTimer>
#include <QVBoxLayout>
SettingsWidget::SettingsWidget(QWidget *parent) : QWidget{parent}
{
setupUI();
loadSettings();
connectSignals();
}
void SettingsWidget::setupUI()
{
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20, 20, 20, 20);
mainLayout->setSpacing(15);
// Create scroll area for the settings
auto *scrollArea = new QScrollArea();
auto *scrollWidget = new QWidget();
auto *scrollLayout = new QVBoxLayout(scrollWidget);
// === GENERAL SETTINGS ===
auto *generalGroup = new QGroupBox("General");
auto *generalLayout = new QVBoxLayout(generalGroup);
// Download path
auto *downloadLayout = new QHBoxLayout();
downloadLayout->addWidget(new QLabel("Download Path:"));
m_downloadPathEdit = new QLineEdit();
m_downloadPathEdit->setReadOnly(true);
downloadLayout->addWidget(m_downloadPathEdit);
auto *browseButton = new QPushButton("Browse...");
downloadLayout->addWidget(browseButton);
generalLayout->addLayout(downloadLayout);
connect(browseButton, &QPushButton::clicked, this,
&SettingsWidget::onBrowseButtonClicked);
// Auto-check for updates
m_autoUpdateCheck = new QCheckBox("Automatically check for updates");
generalLayout->addWidget(m_autoUpdateCheck);
// Theme selection
auto *themeLayout = new QHBoxLayout();
themeLayout->addWidget(new QLabel("Theme:"));
m_themeCombo = new QComboBox();
m_themeCombo->addItems({"System Default", "Light", "Dark"});
themeLayout->addWidget(m_themeCombo);
themeLayout->addStretch();
generalLayout->addLayout(themeLayout);
scrollLayout->addWidget(generalGroup);
// === DEVICE CONNECTION SETTINGS ===
auto *deviceGroup = new QGroupBox("Device Connection");
auto *deviceLayout = new QVBoxLayout(deviceGroup);
// Connection timeout
auto *timeoutLayout = new QHBoxLayout();
timeoutLayout->addWidget(new QLabel("Connection Timeout:"));
m_connectionTimeout = new QSpinBox();
m_connectionTimeout->setRange(5, 60);
m_connectionTimeout->setSuffix(" seconds");
timeoutLayout->addWidget(m_connectionTimeout);
timeoutLayout->addStretch();
deviceLayout->addLayout(timeoutLayout);
// Auto-detect devices
m_autoDetectDevices =
new QCheckBox("Automatically detect connected devices");
deviceLayout->addWidget(m_autoDetectDevices);
// Show device notifications
m_showDeviceNotifications =
new QCheckBox("Show notifications when devices connect/disconnect");
deviceLayout->addWidget(m_showDeviceNotifications);
scrollLayout->addWidget(deviceGroup);
// === DEVELOPER DISK IMAGES ===
auto *diskImageGroup = new QGroupBox("Developer Disk Images");
auto *diskImageLayout = new QVBoxLayout(diskImageGroup);
// Auto-mount compatible images
m_autoMountImages =
new QCheckBox("Automatically mount compatible developer disk images");
diskImageLayout->addWidget(m_autoMountImages);
// Auto-download missing images
m_autoDownloadImages =
new QCheckBox("Automatically download missing compatible images");
diskImageLayout->addWidget(m_autoDownloadImages);
// Verify image signatures
m_verifySignatures =
new QCheckBox("Verify image signatures before mounting");
diskImageLayout->addWidget(m_verifySignatures);
scrollLayout->addWidget(diskImageGroup);
// === FILE OPERATIONS ===
auto *fileGroup = new QGroupBox("File Operations");
auto *fileLayout = new QVBoxLayout(fileGroup);
// Show hidden files
m_showHiddenFiles = new QCheckBox("Show hidden files and folders");
fileLayout->addWidget(m_showHiddenFiles);
// Confirm file deletions
m_confirmDeletions = new QCheckBox("Confirm before deleting files");
fileLayout->addWidget(m_confirmDeletions);
// Max concurrent downloads
auto *downloadsLayout = new QHBoxLayout();
downloadsLayout->addWidget(new QLabel("Maximum concurrent downloads:"));
m_maxDownloads = new QSpinBox();
m_maxDownloads->setRange(1, 10);
downloadsLayout->addWidget(m_maxDownloads);
downloadsLayout->addStretch();
fileLayout->addLayout(downloadsLayout);
scrollLayout->addWidget(fileGroup);
// === ADVANCED SETTINGS ===
auto *advancedGroup = new QGroupBox("Advanced");
auto *advancedLayout = new QVBoxLayout(advancedGroup);
// Debug logging
m_enableDebugLogging = new QCheckBox("Enable debug logging");
advancedLayout->addWidget(m_enableDebugLogging);
// Keep log files
auto *logLayout = new QHBoxLayout();
logLayout->addWidget(new QLabel("Keep log files for:"));
m_logRetention = new QSpinBox();
m_logRetention->setRange(1, 365);
m_logRetention->setSuffix(" days");
logLayout->addWidget(m_logRetention);
logLayout->addStretch();
advancedLayout->addLayout(logLayout);
// Expert mode
m_expertMode = new QCheckBox("Enable expert mode (shows advanced options)");
advancedLayout->addWidget(m_expertMode);
scrollLayout->addWidget(advancedGroup);
// Add stretch to push everything to the top
scrollLayout->addStretch();
scrollArea->setWidget(scrollWidget);
scrollArea->setWidgetResizable(true);
scrollArea->setFrameStyle(QFrame::NoFrame);
mainLayout->addWidget(scrollArea);
// === BOTTOM BUTTONS ===
auto *buttonLayout = new QHBoxLayout();
m_checkUpdatesButton = new QPushButton("Check for Updates");
m_resetButton = new QPushButton("Reset to Defaults");
m_applyButton = new QPushButton("Apply");
m_okButton = new QPushButton("OK");
m_cancelButton = new QPushButton("Cancel");
buttonLayout->addWidget(m_checkUpdatesButton);
buttonLayout->addStretch();
buttonLayout->addWidget(m_resetButton);
buttonLayout->addWidget(m_applyButton);
buttonLayout->addWidget(m_okButton);
buttonLayout->addWidget(m_cancelButton);
mainLayout->addLayout(buttonLayout);
// Connect button signals
connect(m_checkUpdatesButton, &QPushButton::clicked, this,
&SettingsWidget::onCheckUpdatesClicked);
connect(m_resetButton, &QPushButton::clicked, this,
&SettingsWidget::onResetToDefaultsClicked);
connect(m_applyButton, &QPushButton::clicked, this,
&SettingsWidget::onApplyClicked);
connect(m_okButton, &QPushButton::clicked, this,
&SettingsWidget::onOkClicked);
connect(m_cancelButton, &QPushButton::clicked, this,
&SettingsWidget::onCancelClicked);
}
void SettingsWidget::loadSettings()
{
// TODO: Load from SettingsManager
// m_downloadPathEdit->setText(SettingsManager::sharedInstance()->downloadPath());
// m_autoUpdateCheck->setChecked(SettingsManager::sharedInstance()->autoCheckUpdates());
// etc...
}
void SettingsWidget::connectSignals()
{
// Connect all checkboxes and combos for immediate feedback
connect(m_autoUpdateCheck, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_themeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &SettingsWidget::onSettingChanged);
connect(m_connectionTimeout, QOverload<int>::of(&QSpinBox::valueChanged),
this, &SettingsWidget::onSettingChanged);
connect(m_autoDetectDevices, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_showDeviceNotifications, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_autoMountImages, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_autoDownloadImages, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_verifySignatures, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_showHiddenFiles, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_confirmDeletions, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_maxDownloads, QOverload<int>::of(&QSpinBox::valueChanged), this,
&SettingsWidget::onSettingChanged);
connect(m_enableDebugLogging, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
connect(m_logRetention, QOverload<int>::of(&QSpinBox::valueChanged), this,
&SettingsWidget::onSettingChanged);
connect(m_expertMode, &QCheckBox::toggled, this,
&SettingsWidget::onSettingChanged);
}
void SettingsWidget::onBrowseButtonClicked()
{
QString dir = QFileDialog::getExistingDirectory(
this, "Select Download Directory", m_downloadPathEdit->text(),
QFileDialog::ShowDirsOnly);
if (!dir.isEmpty()) {
m_downloadPathEdit->setText(dir);
onSettingChanged();
}
}
void SettingsWidget::onCheckUpdatesClicked()
{
// TODO: Implement update checking logic
m_checkUpdatesButton->setText("Checking...");
m_checkUpdatesButton->setEnabled(false);
// Simulate check (replace with actual update check)
QTimer::singleShot(2000, this, [this]() {
m_checkUpdatesButton->setText("Check for Updates");
m_checkUpdatesButton->setEnabled(true);
QMessageBox::information(this, "Updates",
"You are running the latest version.");
});
}
void SettingsWidget::onResetToDefaultsClicked()
{
auto reply = QMessageBox::question(
this, "Reset Settings",
"Are you sure you want to reset all settings to their default values?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetToDefaults();
}
}
void SettingsWidget::onApplyClicked()
{
saveSettings();
QMessageBox::information(this, "Settings", "Settings have been applied.");
}
void SettingsWidget::onOkClicked()
{
saveSettings();
close();
}
void SettingsWidget::onCancelClicked() { close(); }
void SettingsWidget::onSettingChanged()
{
// Enable apply button when settings change
m_applyButton->setEnabled(true);
}
void SettingsWidget::saveSettings()
{
// TODO: Save to SettingsManager
// SettingsManager::sharedInstance()->setDownloadPath(m_downloadPathEdit->text());
// SettingsManager::sharedInstance()->setAutoCheckUpdates(m_autoUpdateCheck->isChecked());
// etc...
m_applyButton->setEnabled(false);
}
void SettingsWidget::resetToDefaults()
{
// TODO: Reset all controls to default values
// m_downloadPathEdit->setText(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
// m_autoUpdateCheck->setChecked(true);
// etc...
onSettingChanged();
}
+70
View File
@@ -0,0 +1,70 @@
#ifndef SETTINGSWIDGET_H
#define SETTINGSWIDGET_H
#include <QWidget>
// Forward declarations
class QLineEdit;
class QCheckBox;
class QComboBox;
class QSpinBox;
class QPushButton;
class SettingsWidget : public QWidget
{
Q_OBJECT
public:
explicit SettingsWidget(QWidget *parent = nullptr);
private slots:
void onBrowseButtonClicked();
void onCheckUpdatesClicked();
void onResetToDefaultsClicked();
void onApplyClicked();
void onOkClicked();
void onCancelClicked();
void onSettingChanged();
private:
void setupUI();
void loadSettings();
void saveSettings();
void connectSignals();
void resetToDefaults();
// UI Elements
// General
QLineEdit *m_downloadPathEdit;
QCheckBox *m_autoUpdateCheck;
QComboBox *m_themeCombo;
// Device Connection
QSpinBox *m_connectionTimeout;
QCheckBox *m_autoDetectDevices;
QCheckBox *m_showDeviceNotifications;
// Developer Disk Images
QCheckBox *m_autoMountImages;
QCheckBox *m_autoDownloadImages;
QCheckBox *m_verifySignatures;
// File Operations
QCheckBox *m_showHiddenFiles;
QCheckBox *m_confirmDeletions;
QSpinBox *m_maxDownloads;
// Advanced
QCheckBox *m_enableDebugLogging;
QSpinBox *m_logRetention;
QCheckBox *m_expertMode;
// Buttons
QPushButton *m_checkUpdatesButton;
QPushButton *m_resetButton;
QPushButton *m_applyButton;
QPushButton *m_okButton;
QPushButton *m_cancelButton;
};
#endif // SETTINGSWIDGET_H