From 6fe6245be908772af2de1865a84ed853ceee3802 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Fri, 12 Sep 2025 18:31:56 +0000 Subject: [PATCH] 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. --- src/appcontext.cpp | 6 +- src/clickablewidget.h | 28 +++ src/devdiskimageswidget.cpp | 51 +++--- src/devdiskimageswidget.h | 1 - src/devdiskmanager.cpp | 184 ++++++++++++++------ src/devdiskmanager.h | 24 ++- src/devicemanagerwidget.cpp | 216 +++++++++++++++++++++++ src/devicemanagerwidget.h | 52 ++++++ src/devicesidebarwidget.cpp | 331 ++++++++++++++++++++++++++++++++++++ src/devicesidebarwidget.h | 98 +++++++++++ src/devicetabwidget.cpp | 169 ------------------ src/devicetabwidget.h | 31 ---- src/main.cpp | 5 + src/mainwindow.cpp | 240 ++++---------------------- src/mainwindow.h | 9 +- src/mainwindow.ui | 27 +-- src/settingsmanager.cpp | 22 +++ src/settingsmanager.h | 23 +++ src/settingswidget.cpp | 320 ++++++++++++++++++++++++++++++++++ src/settingswidget.h | 70 ++++++++ 20 files changed, 1380 insertions(+), 527 deletions(-) create mode 100644 src/clickablewidget.h create mode 100644 src/devicemanagerwidget.cpp create mode 100644 src/devicemanagerwidget.h create mode 100644 src/devicesidebarwidget.cpp create mode 100644 src/devicesidebarwidget.h delete mode 100644 src/devicetabwidget.cpp delete mode 100644 src/devicetabwidget.h create mode 100644 src/settingsmanager.cpp create mode 100644 src/settingsmanager.h create mode 100644 src/settingswidget.cpp create mode 100644 src/settingswidget.h diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 26e6a68..acc5e98 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -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; } diff --git a/src/clickablewidget.h b/src/clickablewidget.h new file mode 100644 index 0000000..756e16c --- /dev/null +++ b/src/clickablewidget.h @@ -0,0 +1,28 @@ +#ifndef CLICKABLEWIDGET_H +#define CLICKABLEWIDGET_H + +#include +#include + +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 \ No newline at end of file diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 9fa8f86..1efc539 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -2,6 +2,7 @@ #include "appcontext.h" #include "devdiskmanager.h" #include "iDescriptor.h" +#include "settingsmanager.h" #include #include #include @@ -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."); -} \ No newline at end of file +} diff --git a/src/devdiskimageswidget.h b/src/devdiskimageswidget.h index f5b02e2..6f9cc5d 100644 --- a/src/devdiskimageswidget.h +++ b/src/devdiskimageswidget.h @@ -67,7 +67,6 @@ private: QPushButton *m_mountButton; QPushButton *m_check_mountedButton; - QString m_downloadPath; iDescriptorDevice *m_currentDevice; QStringList m_compatibleVersions; QStringList m_otherVersions; diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp index d99a960..fba7c7c 100644 --- a/src/devdiskmanager.cpp +++ b/src/devdiskmanager.cpp @@ -1,5 +1,6 @@ #include "devdiskmanager.h" #include "iDescriptor.h" +#include "settingsmanager.h" #include #include #include @@ -71,24 +72,19 @@ QMap> 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> imageFiles = parseDiskDir(); GetImagesSortedResult sortedResult = getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion, - downloadPath, mounted_sig, mounted_sig_len); + mounted_sig, mounted_sig_len); sortVersions(sortedResult); QList compatibleResult; @@ -129,8 +125,7 @@ void DevDiskManager::sortVersions(GetImagesSortedResult &sortedResult) GetImagesSortedResult DevDiskManager::getImagesSorted( QMap> 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 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 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; } diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h index 86c7acc..5a52c2a 100644 --- a/src/devdiskmanager.h +++ b/src/devdiskmanager.h @@ -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 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> 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 diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp new file mode 100644 index 0000000..6cce9e7 --- /dev/null +++ b/src/devicemanagerwidget.cpp @@ -0,0 +1,216 @@ +#include "devicemanagerwidget.h" +#include "appcontext.h" +#include "devicemenuwidget.h" +#include + +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( + // 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 &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 §ion) +{ + 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 §ion) +{ + if (deviceUuid != m_currentDeviceUuid) { + setCurrentDevice(deviceUuid); + } + + QWidget *tabWidget = m_deviceWidgets[deviceUuid].first; + DeviceMenuWidget *deviceMenuWidget = + qobject_cast(tabWidget); + + if (deviceMenuWidget) { + // Call a method to change the internal tab + deviceMenuWidget->switchToTab(section); + } + // if (deviceIndex != m_currentDeviceIndex) { + // setCurrentDevice(deviceIndex); + // } + // emit sidebarNavigationChanged(deviceUuid, section); +} diff --git a/src/devicemanagerwidget.h b/src/devicemanagerwidget.h new file mode 100644 index 0000000..78c938d --- /dev/null +++ b/src/devicemanagerwidget.h @@ -0,0 +1,52 @@ +#ifndef DEVICEMANAGERWIDGET_H +#define DEVICEMANAGERWIDGET_H + +#include "devicemenuwidget.h" +#include "devicesidebarwidget.h" +#include "iDescriptor.h" +#include +#include +#include +#include + +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 §ion); + +signals: + void deviceChanged(std::string deviceUuid); + void sidebarNavigationChanged(std::string deviceUuid, + const QString §ion); + void updateNoDevicesConnected(); +private slots: + void onSidebarDeviceChanged(std::string deviceUuid); + void onSidebarNavigationChanged(std::string deviceUuid, + const QString §ion); + +private: + void setupUI(); + + QHBoxLayout *m_mainLayout; + DeviceSidebarWidget *m_sidebar; + QStackedWidget *m_stackedWidget; + + QMap> + m_deviceWidgets; // Map to store devices by UDID + std::string m_currentDeviceUuid; +}; + +#endif // DEVICEMANAGERWIDGET_H \ No newline at end of file diff --git a/src/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp new file mode 100644 index 0000000..76a74f5 --- /dev/null +++ b/src/devicesidebarwidget.cpp @@ -0,0 +1,331 @@ +#include "devicesidebarwidget.h" +#include "clickablewidget.h" +#include +#include + +// 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 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(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 §ion) +{ + // for (DeviceSidebarItem *item : m_deviceItems) { + // if (item->getDeviceIndex() == deviceIndex) { + // // Find and check the appropriate button + // QPushButton *targetButton = nullptr; + // if (section == "Info") + // targetButton = item->findChild(); + // else if (section == "Apps") + // targetButton = item->findChildren().value(1); + // else if (section == "Gallery") + // targetButton = item->findChildren().value(2); + // else if (section == "Files") + // targetButton = item->findChildren().value(3); + + // if (targetButton) { + // targetButton->setChecked(true); + // } + // break; + // } + // } +} + +void DeviceSidebarWidget::onDeviceSelected(std::string uuid) +{ + setCurrentDevice(uuid); +} + +void DeviceSidebarWidget::onSidebarNavigationChanged(std::string uuid, + const QString §ion) +{ + 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); + } +} \ No newline at end of file diff --git a/src/devicesidebarwidget.h b/src/devicesidebarwidget.h new file mode 100644 index 0000000..e7bc866 --- /dev/null +++ b/src/devicesidebarwidget.h @@ -0,0 +1,98 @@ +#ifndef DEVICESIDEBARWIDGET_H +#define DEVICESIDEBARWIDGET_H + +#include "clickablewidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 §ion); + +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 §ion); + void updateSidebar(std::string uuid); + +public slots: + void onSidebarNavigationChanged(std::string uuid, const QString §ion); + +signals: + void sidebarNavigationChanged(std::string uuid, const QString §ion); + void deviceNavigationChanged(std::string uuid, const QString §ion); + 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 m_deviceSidebarItems; +}; + +#endif // DEVICESIDEBARWIDGET_H \ No newline at end of file diff --git a/src/devicetabwidget.cpp b/src/devicetabwidget.cpp deleted file mode 100644 index 5353f5d..0000000 --- a/src/devicetabwidget.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "devicetabwidget.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -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("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 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); } \ No newline at end of file diff --git a/src/devicetabwidget.h b/src/devicetabwidget.h deleted file mode 100644 index 2720611..0000000 --- a/src/devicetabwidget.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef DEVICETABWIDGET_H -#define DEVICETABWIDGET_H - -#include -#include -#include - -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 diff --git a/src/main.cpp b/src/main.cpp index fd3e533..2b63627 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d951a5e..03270ad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" #include "detailwindow.h" +#include "settingswidget.h" #include #include #include @@ -13,7 +14,7 @@ #include #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(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(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(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(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(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( - 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(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(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() diff --git a/src/mainwindow.h b/src/mainwindow.h index 725fb3d..c9d1e68 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -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 - m_device_menu_widgets; // Map to store devices by UDID + void updateNoDevicesConnected(); + Ui::MainWindow *ui; + DeviceManagerWidget *m_deviceManager; // Add this member }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index dfea968..8064bbe 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -98,42 +98,25 @@ - - - - - - Tab 1 - - - - - Tab 2 - - - - - + - + Apps - - + Toolbox - - + - + Jailbroken diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp new file mode 100644 index 0000000..ecc4098 --- /dev/null +++ b/src/settingsmanager.cpp @@ -0,0 +1,22 @@ +#include "settingsmanager.h" +#include + +#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(); +} \ No newline at end of file diff --git a/src/settingsmanager.h b/src/settingsmanager.h new file mode 100644 index 0000000..29f0ef9 --- /dev/null +++ b/src/settingsmanager.h @@ -0,0 +1,23 @@ +#ifndef SETTINGSMANAGER_H +#define SETTINGSMANAGER_H + +#include +#include + +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 diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp new file mode 100644 index 0000000..0d2d338 --- /dev/null +++ b/src/settingswidget.cpp @@ -0,0 +1,320 @@ +#include "settingswidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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::of(&QComboBox::currentIndexChanged), + this, &SettingsWidget::onSettingChanged); + connect(m_connectionTimeout, QOverload::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::of(&QSpinBox::valueChanged), this, + &SettingsWidget::onSettingChanged); + connect(m_enableDebugLogging, &QCheckBox::toggled, this, + &SettingsWidget::onSettingChanged); + connect(m_logRetention, QOverload::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(); +} diff --git a/src/settingswidget.h b/src/settingswidget.h new file mode 100644 index 0000000..54b06fb --- /dev/null +++ b/src/settingswidget.h @@ -0,0 +1,70 @@ +#ifndef SETTINGSWIDGET_H +#define SETTINGSWIDGET_H + +#include + +// 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