mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
fix bugs & add new dialogs
- Introduced `CredDialog` to prompt users for access to secure backends (Windows Credential Manager or Secret Service). - Integrated `CredDialog` into `AppsWidget` initialization flow, allowing users to skip signing in. - Updated `SettingsManager` to manage user preferences for showing the keychain dialog. - Enhanced `DevDiskManager` and related classes to utilize a new method for creating device disk image paths. - Refactored `VirtualLocation` to support saving and displaying recent locations. - Improved UI responsiveness and layout adjustments across various widgets. - Cleaned up unused code and comments for better maintainability.
This commit is contained in:
+16
-6
@@ -23,6 +23,7 @@
|
||||
#include "appdownloaddialog.h"
|
||||
#include "appinstalldialog.h"
|
||||
#include "appstoremanager.h"
|
||||
#include "creddialog.h"
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "keychaindialog.h"
|
||||
#include "logindialog.h"
|
||||
@@ -228,6 +229,7 @@ void AppsWidget::setupUI()
|
||||
|
||||
void AppsWidget::init()
|
||||
{
|
||||
// FIXME:update url
|
||||
QUrl sponsorsUrl("http://localhost:5173/sponsors.json");
|
||||
QNetworkRequest request(sponsorsUrl);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
@@ -260,8 +262,6 @@ void AppsWidget::init()
|
||||
QJsonObject silverObj = sponsorObj["silver"].toObject();
|
||||
QJsonObject bronzeObj = sponsorObj["bronze"].toObject();
|
||||
|
||||
// Store the platinum members to be used when populating
|
||||
// the grid
|
||||
m_platinumSponsors = platinumObj["members"].toArray();
|
||||
m_goldSponsors = goldObj["members"].toArray();
|
||||
m_silverSponsors = silverObj["members"].toArray();
|
||||
@@ -296,6 +296,12 @@ void AppsWidget::handleInit()
|
||||
"4px; padding: 8px 16px; font-size: 14px;");
|
||||
return;
|
||||
}
|
||||
/*
|
||||
FIXME: ipatoolinitialze still uses the secure backends
|
||||
when if the user rejects it, the moment he/she tries to sign in
|
||||
prompt(keychain or secret-service whatever the backend is) will be seen
|
||||
again
|
||||
*/
|
||||
if (!SettingsManager::sharedInstance()->useUnsecureBackend() &&
|
||||
SettingsManager::sharedInstance()->showKeychainDialog()) {
|
||||
#ifdef __APPLE__
|
||||
@@ -306,9 +312,16 @@ void AppsWidget::handleInit()
|
||||
showDefaultApps();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
CredDialog dialog(this);
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
// pass empty QJsonObject to skip signing in
|
||||
onAppStoreInitialized(QJsonObject());
|
||||
showDefaultApps();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// todo also change in the ipatoolinitialze as the backend is alrady enabled
|
||||
onAppStoreInitialized(m_manager->getAccountInfo());
|
||||
showDefaultApps();
|
||||
}
|
||||
@@ -437,7 +450,6 @@ void AppsWidget::populateDefaultApps()
|
||||
int col = 0;
|
||||
const int maxCols = 3;
|
||||
|
||||
// Helper lambda to advance the grid position
|
||||
auto advanceGridPos = [&]() {
|
||||
col++;
|
||||
if (col >= maxCols) {
|
||||
@@ -612,7 +624,6 @@ void AppsWidget::createAppCard(
|
||||
QUrl url(logoUrl);
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
// Use Qt's parent-child relationship to auto-cleanup
|
||||
connect(
|
||||
reply, &QNetworkReply::finished, this, [reply, safeIconLabel]() {
|
||||
if (reply->error() == QNetworkReply::NoError && safeIconLabel) {
|
||||
@@ -638,7 +649,6 @@ void AppsWidget::createAppCard(
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
// Ensure reply is deleted if iconLabel is destroyed
|
||||
connect(iconLabel, &QObject::destroyed, reply, &QNetworkReply::abort);
|
||||
} else if (!bundleId.isEmpty()) {
|
||||
fetchAppIconFromApple(
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* iDescriptor: A free and open-source idevice management tool.
|
||||
*
|
||||
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "creddialog.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
CredDialog::CredDialog(QWidget *parent)
|
||||
: QDialog(parent), m_mainLayout(nullptr), m_okButton(nullptr),
|
||||
m_titleLabel(nullptr), m_descriptionLabel(nullptr),
|
||||
m_dontShowAgainCheckbox(nullptr)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
CredDialog::~CredDialog() {}
|
||||
|
||||
void CredDialog::setupUI()
|
||||
{
|
||||
#ifdef WIN32
|
||||
setWindowTitle("Windows Credential Manager Access Required");
|
||||
#else
|
||||
setWindowTitle("Secret Service Access Required");
|
||||
#endif
|
||||
setModal(true);
|
||||
setMinimumSize(500, 250);
|
||||
resize(600, 300);
|
||||
|
||||
m_mainLayout = new QVBoxLayout(this);
|
||||
m_mainLayout->setContentsMargins(20, 20, 20, 20);
|
||||
m_mainLayout->setSpacing(15);
|
||||
|
||||
// Title label
|
||||
#ifdef WIN32
|
||||
m_titleLabel = new QLabel("Windows Credential Manager Access Required");
|
||||
#else
|
||||
m_titleLabel = new QLabel("Secret Service Access Required");
|
||||
#endif
|
||||
m_titleLabel->setAlignment(Qt::AlignCenter);
|
||||
m_titleLabel->setStyleSheet(
|
||||
"font-size: 18px; font-weight: bold; margin-bottom: 10px;");
|
||||
m_mainLayout->addWidget(m_titleLabel);
|
||||
|
||||
// Description label
|
||||
#ifdef WIN32
|
||||
QString description =
|
||||
"In order to sign in to App Store we use the Windows Credential "
|
||||
"Manager "
|
||||
"to safely store and retrieve your credentials. You may be prompted to "
|
||||
"allow access to the credential manager. "
|
||||
"This is a security feature to protect your Apple ID credentials. You "
|
||||
"can disable this in Settings.";
|
||||
#else
|
||||
QString description =
|
||||
"In order to sign in to App Store we use the Secret Service "
|
||||
"(gnome-keyring "
|
||||
"or similar) to safely store and retrieve your credentials. You may be "
|
||||
"prompted to unlock your keyring or allow access. "
|
||||
"This is a security feature to protect your Apple ID credentials. You "
|
||||
"can disable this in Settings.";
|
||||
#endif
|
||||
|
||||
m_descriptionLabel = new QLabel(description);
|
||||
m_descriptionLabel->setAlignment(Qt::AlignCenter);
|
||||
m_descriptionLabel->setWordWrap(true);
|
||||
m_descriptionLabel->setStyleSheet("font-size: 14px; margin: 10px;");
|
||||
m_mainLayout->addWidget(m_descriptionLabel);
|
||||
|
||||
m_mainLayout->addStretch();
|
||||
|
||||
m_dontShowAgainCheckbox = new QCheckBox("Don't show this again");
|
||||
m_mainLayout->addWidget(m_dontShowAgainCheckbox, 0, Qt::AlignCenter);
|
||||
|
||||
QHBoxLayout *buttonsLayout = new QHBoxLayout();
|
||||
m_skipSigningInButton = new QPushButton("Skip For Now");
|
||||
m_skipSigningInButton->setFixedHeight(40);
|
||||
|
||||
m_okButton = new QPushButton("OK, I understand");
|
||||
m_okButton->setDefault(true);
|
||||
m_okButton->setFixedHeight(40);
|
||||
|
||||
buttonsLayout->addWidget(m_skipSigningInButton);
|
||||
buttonsLayout->addWidget(m_okButton);
|
||||
|
||||
m_mainLayout->addLayout(buttonsLayout, Qt::AlignCenter);
|
||||
|
||||
connect(m_okButton, &QPushButton::clicked, this, &CredDialog::onOkClicked);
|
||||
connect(m_skipSigningInButton, &QPushButton::clicked, this,
|
||||
&CredDialog::onSkipSigningInClicked);
|
||||
}
|
||||
|
||||
void CredDialog::onOkClicked()
|
||||
{
|
||||
if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) {
|
||||
SettingsManager::sharedInstance()->setShowKeychainDialog(false);
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void CredDialog::onSkipSigningInClicked()
|
||||
{
|
||||
if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) {
|
||||
SettingsManager::sharedInstance()->setShowKeychainDialog(false);
|
||||
}
|
||||
|
||||
reject();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* iDescriptor: A free and open-source idevice management tool.
|
||||
*
|
||||
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CRED_DIALOG_H
|
||||
#define CRED_DIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class CredDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CredDialog(QWidget *parent = nullptr);
|
||||
~CredDialog();
|
||||
|
||||
private slots:
|
||||
void onOkClicked();
|
||||
void onSkipSigningInClicked();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QVBoxLayout *m_mainLayout;
|
||||
QPushButton *m_okButton;
|
||||
QPushButton *m_skipSigningInButton;
|
||||
QLabel *m_titleLabel;
|
||||
QLabel *m_descriptionLabel;
|
||||
QCheckBox *m_dontShowAgainCheckbox;
|
||||
};
|
||||
|
||||
#endif // CRED_DIALOG_H
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "devdiskimagehelper.h"
|
||||
#include "devdiskmanager.h"
|
||||
#include "qprocessindicator.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
@@ -31,10 +32,9 @@ DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device,
|
||||
: QDialog(parent), m_device(device), m_isDownloading(false),
|
||||
m_isMounting(false)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle("Developer Disk Image - iDescriptor");
|
||||
setupUI();
|
||||
|
||||
QTimer::singleShot(0, this, &DevDiskImageHelper::start);
|
||||
}
|
||||
|
||||
void DevDiskImageHelper::setupUI()
|
||||
@@ -96,12 +96,18 @@ void DevDiskImageHelper::start()
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
|
||||
// we dont have developer disk images for ios 6 and below
|
||||
// FIXME:we dont have developer disk images for ios 6 and below
|
||||
if (deviceMajorVersion > 5) {
|
||||
// TODO: maybe check isMountAvailable and finishWithFailure if false
|
||||
const bool isMountAvailable =
|
||||
DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
|
||||
QTimer::singleShot(500, this, &DevDiskImageHelper::checkAndMount);
|
||||
DevDiskManager::sharedInstance()->downloadCompatibleImage(
|
||||
m_device, [this](bool success) {
|
||||
if (success) {
|
||||
checkAndMount();
|
||||
} else {
|
||||
finishWithError("Failed to download compatible image.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
finishWithSuccess();
|
||||
return;
|
||||
@@ -122,7 +128,6 @@ void DevDiskImageHelper::checkAndMount()
|
||||
|
||||
// If image is already mounted
|
||||
if (!result.sig.empty()) {
|
||||
showStatus("Developer disk image already mounted");
|
||||
finishWithSuccess();
|
||||
return;
|
||||
}
|
||||
@@ -132,6 +137,7 @@ void DevDiskImageHelper::checkAndMount()
|
||||
|
||||
void DevDiskImageHelper::onMountButtonClicked()
|
||||
{
|
||||
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
|
||||
m_mountButton->setVisible(false);
|
||||
m_loadingIndicator->start();
|
||||
m_isMounting = true;
|
||||
@@ -142,7 +148,7 @@ void DevDiskImageHelper::onMountButtonClicked()
|
||||
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
|
||||
QList<ImageInfo> images = DevDiskManager::sharedInstance()->parseImageList(
|
||||
deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
path, deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
|
||||
// Check if compatible image is downloaded
|
||||
bool hasDownloadedImage = false;
|
||||
|
||||
@@ -195,9 +195,10 @@ void DevDiskImagesWidget::displayImages()
|
||||
qDebug() << "Device version:" << deviceMajorVersion << "."
|
||||
<< deviceMinorVersion << "displayImages";
|
||||
// Parse images using manager
|
||||
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
|
||||
QList<ImageInfo> allImages =
|
||||
DevDiskManager::sharedInstance()->parseImageList(
|
||||
deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(),
|
||||
path, deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(),
|
||||
m_mounted_sig_len);
|
||||
|
||||
qDebug() << "Total images:" << allImages.size();
|
||||
|
||||
+29
-41
@@ -134,24 +134,6 @@ QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Trustcache URLs (for iOS 17+)
|
||||
if (versionData.contains("Trustcache")) {
|
||||
QJsonArray trustcacheArray = versionData["Trustcache"].toArray();
|
||||
if (!trustcacheArray.isEmpty()) {
|
||||
versionFiles["Image.dmg.trustcache"] =
|
||||
trustcacheArray[0].toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle BuildManifest URLs (for iOS 17+)
|
||||
if (versionData.contains("BuildManifest")) {
|
||||
QJsonArray manifestArray = versionData["BuildManifest"].toArray();
|
||||
if (!manifestArray.isEmpty()) {
|
||||
versionFiles["BuildManifest.plist"] =
|
||||
manifestArray[0].toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Only add versions that have at least an image file
|
||||
if (!versionFiles.isEmpty() &&
|
||||
versionFiles.contains("DeveloperDiskImage.dmg")) {
|
||||
@@ -162,7 +144,8 @@ QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
|
||||
return imageFiles;
|
||||
}
|
||||
|
||||
QList<ImageInfo> DevDiskManager::parseImageList(int deviceMajorVersion,
|
||||
QList<ImageInfo> DevDiskManager::parseImageList(QString path,
|
||||
int deviceMajorVersion,
|
||||
int deviceMinorVersion,
|
||||
const char *mounted_sig,
|
||||
uint64_t mounted_sig_len)
|
||||
@@ -171,15 +154,16 @@ QList<ImageInfo> DevDiskManager::parseImageList(int deviceMajorVersion,
|
||||
|
||||
QMap<QString, QMap<QString, QString>> imageFiles = parseDiskDir();
|
||||
QList<ImageInfo> sortedResult =
|
||||
getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion,
|
||||
mounted_sig, mounted_sig_len);
|
||||
getImagesSorted(imageFiles, path, deviceMajorVersion,
|
||||
deviceMinorVersion, mounted_sig, mounted_sig_len);
|
||||
|
||||
return sortedResult;
|
||||
}
|
||||
|
||||
QList<ImageInfo> DevDiskManager::getImagesSorted(
|
||||
QMap<QString, QMap<QString, QString>> imageFiles, int deviceMajorVersion,
|
||||
int deviceMinorVersion, const char *mounted_sig, uint64_t mounted_sig_len)
|
||||
QMap<QString, QMap<QString, QString>> imageFiles, QString path,
|
||||
int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig,
|
||||
uint64_t mounted_sig_len)
|
||||
{
|
||||
QList<ImageInfo> allImages;
|
||||
// TODO: what is this ?
|
||||
@@ -194,8 +178,7 @@ QList<ImageInfo> DevDiskManager::getImagesSorted(
|
||||
info.version = version;
|
||||
info.dmgPath = it.value()["DeveloperDiskImage.dmg"];
|
||||
info.sigPath = it.value()["DeveloperDiskImage.dmg.signature"];
|
||||
info.isDownloaded = isImageDownloaded(
|
||||
version, SettingsManager::sharedInstance()->devdiskimgpath());
|
||||
info.isDownloaded = isImageDownloaded(version, path);
|
||||
|
||||
// Determine compatibility
|
||||
if (hasConnectedDevice) {
|
||||
@@ -336,15 +319,17 @@ bool DevDiskManager::isImageDownloaded(const QString &version,
|
||||
return QFile::exists(dmgPath) && QFile::exists(sigPath);
|
||||
}
|
||||
|
||||
bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device,
|
||||
std::function<void(bool)> callback)
|
||||
{
|
||||
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
|
||||
unsigned int device_version = idevice_get_device_version(device->device);
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
qDebug() << "Device version:" << deviceMajorVersion << "."
|
||||
<< deviceMinorVersion;
|
||||
QList<ImageInfo> images =
|
||||
parseImageList(deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
|
||||
for (const ImageInfo &info : images) {
|
||||
if (info.compatibility != ImageCompatibility::Compatible &&
|
||||
@@ -352,8 +337,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
continue;
|
||||
}
|
||||
if (info.isDownloaded) {
|
||||
qDebug() << "There is a compatible image already downloaded:"
|
||||
<< info.version;
|
||||
callback(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -363,6 +347,17 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
if (info.compatibility == ImageCompatibility::Compatible ||
|
||||
info.compatibility == ImageCompatibility::MaybeCompatible) {
|
||||
const QString versionToDownload = info.version;
|
||||
connect(
|
||||
this, &DevDiskManager::imageDownloadFinished, this,
|
||||
[this, versionToDownload,
|
||||
callback](const QString &finishedVersion, bool success,
|
||||
const QString &errorMessage) {
|
||||
if (finishedVersion == versionToDownload) {
|
||||
callback(success);
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
qDebug()
|
||||
<< "No compatible image found locally. Downloading version:"
|
||||
<< versionToDownload;
|
||||
@@ -371,8 +366,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
downloadImage(versionToDownload);
|
||||
auto *downloadItem = new DownloadItem();
|
||||
downloadItem->version = versionToDownload;
|
||||
downloadItem->downloadPath =
|
||||
SettingsManager::sharedInstance()->devdiskimgpath();
|
||||
downloadItem->downloadPath = path;
|
||||
downloadItem->dmgReply = replies.first;
|
||||
downloadItem->sigReply = replies.second;
|
||||
|
||||
@@ -400,12 +394,13 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
// FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
|
||||
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
{
|
||||
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
|
||||
unsigned int device_version = idevice_get_device_version(device->device);
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
|
||||
QList<ImageInfo> images =
|
||||
parseImageList(deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
|
||||
// 1. Try to mount an already downloaded compatible image
|
||||
for (const ImageInfo &info : images) {
|
||||
@@ -431,8 +426,6 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
}
|
||||
}
|
||||
}
|
||||
const QString downloadPath =
|
||||
SettingsManager::sharedInstance()->devdiskimgpath();
|
||||
|
||||
// 2. If none are downloaded, download the newest compatible one
|
||||
for (const ImageInfo &info : images) {
|
||||
@@ -443,19 +436,15 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
<< "No compatible image found locally. Downloading version:"
|
||||
<< versionToDownload;
|
||||
|
||||
// Connect a one-time slot to mount the image after download
|
||||
// finishes
|
||||
connect(
|
||||
this, &DevDiskManager::imageDownloadFinished, this,
|
||||
[this, device, downloadPath,
|
||||
[this, device, path,
|
||||
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);
|
||||
// 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;
|
||||
@@ -468,7 +457,7 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
downloadImage(versionToDownload);
|
||||
auto *downloadItem = new DownloadItem();
|
||||
downloadItem->version = versionToDownload;
|
||||
downloadItem->downloadPath = downloadPath;
|
||||
downloadItem->downloadPath = path;
|
||||
downloadItem->dmgReply = replies.first;
|
||||
downloadItem->sigReply = replies.second;
|
||||
|
||||
@@ -562,7 +551,6 @@ void DevDiskManager::onFileDownloadFinished()
|
||||
QString path = QUrl::fromPercentEncoding(reply->url().path().toUtf8());
|
||||
QFileInfo fileInfo(path);
|
||||
QString filename = fileInfo.fileName();
|
||||
|
||||
QString targetPath = QDir(QDir(item->downloadPath).filePath(item->version))
|
||||
.filePath(filename);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
explicit DevDiskManager(QObject *parent = nullptr);
|
||||
static DevDiskManager *sharedInstance();
|
||||
|
||||
QList<ImageInfo> parseImageList(int deviceMajorVersion,
|
||||
QList<ImageInfo> parseImageList(QString path, int deviceMajorVersion,
|
||||
int deviceMinorVersion,
|
||||
const char *mounted_sig,
|
||||
uint64_t mounted_sig_len);
|
||||
@@ -61,7 +61,8 @@ public:
|
||||
QByteArray getImageListData() const { return m_imageListJsonData; }
|
||||
GetMountedImageResult getMountedImage(const char *udid);
|
||||
bool mountCompatibleImage(iDescriptorDevice *device);
|
||||
bool downloadCompatibleImage(iDescriptorDevice *device);
|
||||
bool downloadCompatibleImage(iDescriptorDevice *device,
|
||||
std::function<void(bool)> callback);
|
||||
|
||||
signals:
|
||||
void imageListFetched(bool success,
|
||||
@@ -93,8 +94,9 @@ private:
|
||||
QMap<QString, QMap<QString, QString>> parseDiskDir();
|
||||
QList<ImageInfo>
|
||||
getImagesSorted(QMap<QString, QMap<QString, QString>> imageFiles,
|
||||
int deviceMajorVersion, int deviceMinorVersion,
|
||||
const char *mounted_sig, uint64_t mounted_sig_len);
|
||||
QString path, int deviceMajorVersion,
|
||||
int deviceMinorVersion, const char *mounted_sig,
|
||||
uint64_t mounted_sig_len);
|
||||
void populateImageList();
|
||||
};
|
||||
|
||||
|
||||
@@ -478,6 +478,7 @@ void GalleryWidget::onBackToAlbums()
|
||||
{
|
||||
// Switch back to album selection view
|
||||
m_stackedWidget->setCurrentWidget(m_albumSelectionWidget);
|
||||
m_model->clear();
|
||||
|
||||
// Disable controls and hide back button
|
||||
setControlsEnabled(false);
|
||||
|
||||
@@ -83,6 +83,12 @@ LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
connect(m_timer, &QTimer::timeout, this,
|
||||
&LiveScreenWidget::updateScreenshot);
|
||||
|
||||
// Defer the initialization to allow the main widget to show first
|
||||
QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization);
|
||||
}
|
||||
|
||||
void LiveScreenWidget::startInitialization()
|
||||
{
|
||||
const bool initializeScreenshotServiceSuccess =
|
||||
initializeScreenshotService(false);
|
||||
if (initializeScreenshotServiceSuccess)
|
||||
|
||||
@@ -47,7 +47,8 @@ private:
|
||||
screenshotr_client_t m_shotrClient;
|
||||
int m_fps;
|
||||
|
||||
signals:
|
||||
private:
|
||||
void startInitialization(); // Add this line
|
||||
};
|
||||
|
||||
#endif // LIVESCREEN_H
|
||||
|
||||
+3
-3
@@ -193,14 +193,14 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
ui->statusbar->addWidget(m_connectedDeviceCountLabel);
|
||||
ui->statusbar->setContentsMargins(0, 0, 0, 0);
|
||||
ui->statusbar->addPermanentWidget(githubButton);
|
||||
ui->statusbar->addPermanentWidget(settingsButton);
|
||||
|
||||
QLabel *appVersionLabel = new QLabel(QString("v%1").arg(APP_VERSION));
|
||||
appVersionLabel->setContentsMargins(5, 0, 5, 0);
|
||||
appVersionLabel->setStyleSheet(
|
||||
"QLabel:hover { background-color : #13131319; }");
|
||||
ui->statusbar->addPermanentWidget(appVersionLabel);
|
||||
ui->statusbar->addPermanentWidget(githubButton);
|
||||
ui->statusbar->addPermanentWidget(settingsButton);
|
||||
|
||||
#ifdef __linux__
|
||||
QList<QString> mounted_iFusePaths = iFuseManager::getMountPoints();
|
||||
|
||||
|
||||
+9
-40
@@ -57,9 +57,8 @@ PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType,
|
||||
&PhotoModel::requestThumbnail, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
PhotoModel::~PhotoModel()
|
||||
void PhotoModel::clear()
|
||||
{
|
||||
qDebug() << "PhotoModel destructor called";
|
||||
// Clean up any active watchers
|
||||
for (auto *watcher : m_activeLoaders.values()) {
|
||||
if (watcher) {
|
||||
@@ -73,6 +72,12 @@ PhotoModel::~PhotoModel()
|
||||
m_thumbnailCache.clear();
|
||||
}
|
||||
|
||||
PhotoModel::~PhotoModel()
|
||||
{
|
||||
qDebug() << "PhotoModel destructor called";
|
||||
clear();
|
||||
}
|
||||
|
||||
QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device,
|
||||
const QString &filePath,
|
||||
const QSize &requestedSize)
|
||||
@@ -446,31 +451,6 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoModel::setThumbnailSize(const QSize &size)
|
||||
{
|
||||
if (m_thumbnailSize != size) {
|
||||
m_thumbnailSize = size;
|
||||
// Clear cache when size changes
|
||||
clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoModel::clearCache()
|
||||
{
|
||||
m_thumbnailCache.clear();
|
||||
|
||||
// Reset all requested flags
|
||||
for (PhotoInfo &info : m_photos) {
|
||||
info.thumbnailRequested = false;
|
||||
}
|
||||
|
||||
// Notify view to refresh
|
||||
if (!m_photos.isEmpty()) {
|
||||
emit dataChanged(createIndex(0, 0), createIndex(m_photos.size() - 1, 0),
|
||||
{Qt::DecorationRole});
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoModel::requestThumbnail(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_photos.size())
|
||||
@@ -897,19 +877,8 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const
|
||||
void PhotoModel::setAlbumPath(const QString &albumPath)
|
||||
{
|
||||
if (m_albumPath != albumPath) {
|
||||
// Clear cache when switching albums to prevent memory buildup
|
||||
clearCache();
|
||||
|
||||
// Cancel any pending thumbnail requests
|
||||
for (auto *watcher : m_activeLoaders.values()) {
|
||||
if (watcher) {
|
||||
watcher->cancel();
|
||||
watcher->waitForFinished();
|
||||
watcher->deleteLater();
|
||||
}
|
||||
}
|
||||
m_activeLoaders.clear();
|
||||
m_loadingPaths.clear();
|
||||
qDebug() << "Setting new album path:" << albumPath;
|
||||
clear();
|
||||
|
||||
m_albumPath = albumPath;
|
||||
populatePhotoPaths();
|
||||
|
||||
+1
-4
@@ -59,10 +59,6 @@ public:
|
||||
QVariant data(const QModelIndex &index,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Thumbnail management
|
||||
void setThumbnailSize(const QSize &size);
|
||||
void clearCache();
|
||||
|
||||
// Album management
|
||||
void setAlbumPath(const QString &albumPath);
|
||||
void refreshPhotos();
|
||||
@@ -89,6 +85,7 @@ public:
|
||||
static QPixmap loadThumbnailFromDevice(iDescriptorDevice *device,
|
||||
const QString &filePath,
|
||||
const QSize &size);
|
||||
void clear();
|
||||
signals:
|
||||
void thumbnailNeedsToBeLoaded(int index);
|
||||
void exportRequested(const QStringList &filePaths);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "settingsmanager.h"
|
||||
#include "settingswidget.h"
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -59,6 +60,17 @@ QString SettingsManager::devdiskimgpath() const
|
||||
return downloadPath(); // Use the new downloadPath method
|
||||
}
|
||||
|
||||
QString SettingsManager::mkDevDiskImgPath() const
|
||||
{
|
||||
QString path = devdiskimgpath();
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(path);
|
||||
return path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Settings implementation
|
||||
QString SettingsManager::downloadPath() const
|
||||
{
|
||||
@@ -291,4 +303,58 @@ void SettingsManager::clearKeys(const QString &keyPrefix)
|
||||
m_settings->sync();
|
||||
|
||||
emit favoritePlacesChanged();
|
||||
}
|
||||
|
||||
void SettingsManager::saveRecentLocation(double latitude, double longitude,
|
||||
const QString &name)
|
||||
{
|
||||
// Get existing recent locations
|
||||
QList<QVariantMap> recentLocations = getRecentLocations();
|
||||
|
||||
// Create new location entry
|
||||
QVariantMap newLocation;
|
||||
newLocation["latitude"] = latitude;
|
||||
newLocation["longitude"] = longitude;
|
||||
|
||||
// Add to front of list
|
||||
recentLocations.prepend(newLocation);
|
||||
|
||||
// Keep only last 10 locations
|
||||
while (recentLocations.size() > 10) {
|
||||
recentLocations.removeLast();
|
||||
}
|
||||
|
||||
// Save to settings
|
||||
QVariantList variantList;
|
||||
for (const QVariantMap &loc : recentLocations) {
|
||||
variantList.append(loc);
|
||||
}
|
||||
|
||||
m_settings->setValue("recentLocations", variantList);
|
||||
m_settings->sync();
|
||||
|
||||
qDebug() << "Saved recent location:" << latitude << "," << longitude;
|
||||
|
||||
emit recentLocationsChanged();
|
||||
}
|
||||
|
||||
QList<QVariantMap> SettingsManager::getRecentLocations() const
|
||||
{
|
||||
QList<QVariantMap> recentLocations;
|
||||
|
||||
QVariantList variantList = m_settings->value("recentLocations").toList();
|
||||
|
||||
for (const QVariant &item : variantList) {
|
||||
if (item.canConvert<QVariantMap>()) {
|
||||
recentLocations.append(item.toMap());
|
||||
}
|
||||
}
|
||||
|
||||
return recentLocations;
|
||||
}
|
||||
|
||||
void SettingsManager::clearRecentLocations()
|
||||
{
|
||||
m_settings->remove("recentLocations");
|
||||
m_settings->sync();
|
||||
}
|
||||
@@ -57,10 +57,17 @@ public:
|
||||
getFavoritePlaces(const QString &keyPrefix) const;
|
||||
void showSettingsDialog();
|
||||
|
||||
// New settings methods
|
||||
// Recently used locations
|
||||
void saveRecentLocation(double latitude, double longitude,
|
||||
const QString &name = QString());
|
||||
QList<QVariantMap> getRecentLocations() const;
|
||||
void clearRecentLocations();
|
||||
|
||||
QString downloadPath() const;
|
||||
void setDownloadPath(const QString &path);
|
||||
|
||||
QString mkDevDiskImgPath() const;
|
||||
|
||||
bool autoCheckUpdates() const;
|
||||
void setAutoCheckUpdates(bool enabled);
|
||||
|
||||
@@ -94,6 +101,7 @@ public:
|
||||
|
||||
signals:
|
||||
void favoritePlacesChanged();
|
||||
void recentLocationsChanged();
|
||||
|
||||
private:
|
||||
QDialog *m_dialog;
|
||||
|
||||
+181
-38
@@ -22,6 +22,7 @@
|
||||
#include "devdiskimagehelper.h"
|
||||
#include "devdiskmanager.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QDoubleValidator>
|
||||
#include <QGeoCoordinate>
|
||||
@@ -44,6 +45,19 @@
|
||||
VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
: QWidget{parent}, m_device(device)
|
||||
{
|
||||
unsigned int device_version = idevice_get_device_version(m_device->device);
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
|
||||
if (deviceMajorVersion > 16) {
|
||||
QMessageBox::warning(
|
||||
this, "Unsupported iOS Version",
|
||||
"Virtual Location feature requires iOS 16 or earlier.\n"
|
||||
"Your device is running iOS " +
|
||||
QString::number(deviceMajorVersion) +
|
||||
", which is not yet supported.");
|
||||
QTimer::singleShot(0, this, &QWidget::close);
|
||||
return;
|
||||
}
|
||||
setWindowTitle("Virtual Location - iDescriptor");
|
||||
// Create the main layout
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
@@ -54,17 +68,17 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
QWidget *rightPanel = new QWidget();
|
||||
rightPanel->setFixedWidth(250);
|
||||
|
||||
QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel);
|
||||
rightLayout->setContentsMargins(15, 15, 15, 15);
|
||||
rightLayout->setSpacing(10);
|
||||
m_rightLayout = new QVBoxLayout(rightPanel);
|
||||
m_rightLayout->setContentsMargins(15, 15, 15, 15);
|
||||
m_rightLayout->setSpacing(10);
|
||||
|
||||
// Title
|
||||
QLabel *titleLabel = new QLabel("Virtual Location Settings");
|
||||
titleLabel->setStyleSheet("margin-bottom: 10px;");
|
||||
rightLayout->addWidget(titleLabel);
|
||||
m_rightLayout->addWidget(titleLabel);
|
||||
|
||||
QGroupBox *coordGroup = new QGroupBox("Coordinates");
|
||||
rightLayout->addWidget(coordGroup);
|
||||
m_rightLayout->addWidget(coordGroup);
|
||||
|
||||
QVBoxLayout *coordLayout = new QVBoxLayout(coordGroup);
|
||||
|
||||
@@ -89,16 +103,24 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
coordLayout->addWidget(m_longitudeEdit);
|
||||
|
||||
// Add some spacing
|
||||
rightLayout->addItem(
|
||||
m_rightLayout->addItem(
|
||||
new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Fixed));
|
||||
|
||||
// Apply button
|
||||
m_applyButton = new QPushButton("Apply Settings");
|
||||
m_applyButton = new QPushButton("Apply Location");
|
||||
m_applyButton->setDefault(true);
|
||||
rightLayout->addWidget(m_applyButton);
|
||||
m_rightLayout->addWidget(m_applyButton);
|
||||
|
||||
// Recent locations section
|
||||
loadRecentLocations(m_rightLayout);
|
||||
|
||||
// Add stretch to push everything to the top
|
||||
rightLayout->addStretch();
|
||||
m_rightLayout->addStretch();
|
||||
|
||||
// Connect to recent locations changes
|
||||
connect(SettingsManager::sharedInstance(),
|
||||
&SettingsManager::recentLocationsChanged, this,
|
||||
&VirtualLocation::refreshRecentLocations);
|
||||
|
||||
// Create map widget
|
||||
m_quickWidget = new QQuickWidget(this);
|
||||
@@ -142,20 +164,7 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
}
|
||||
});
|
||||
|
||||
DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
|
||||
unsigned int device_version = idevice_get_device_version(m_device->device);
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
|
||||
if (deviceMajorVersion > 16) {
|
||||
QMessageBox::warning(
|
||||
this, "Unsupported iOS Version",
|
||||
"Virtual Location feature requires iOS 16 or earlier.\n"
|
||||
"Your device is running iOS " +
|
||||
QString::number(deviceMajorVersion) +
|
||||
", which is not yet supported.");
|
||||
QTimer::singleShot(0, this, &QWidget::close);
|
||||
return;
|
||||
}
|
||||
// DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
|
||||
}
|
||||
|
||||
void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status)
|
||||
@@ -290,6 +299,7 @@ void VirtualLocation::updateInputsFromMap(double latitude, double longitude)
|
||||
|
||||
void VirtualLocation::onApplyClicked()
|
||||
{
|
||||
m_applyButton->setEnabled(false);
|
||||
bool latOk, lonOk;
|
||||
double latitude = m_latitudeEdit->text().toDouble(&latOk);
|
||||
double longitude = m_longitudeEdit->text().toDouble(&lonOk);
|
||||
@@ -298,15 +308,16 @@ void VirtualLocation::onApplyClicked()
|
||||
QMessageBox::warning(
|
||||
this, "Invalid Input",
|
||||
"Please enter valid latitude and longitude values.");
|
||||
m_applyButton->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and show the helper dialog
|
||||
auto *helper = new DevDiskImageHelper(m_device, this);
|
||||
|
||||
connect(helper, &DevDiskImageHelper::mountingCompleted, this,
|
||||
[this, latitude, longitude, helper](bool success) {
|
||||
helper->deleteLater();
|
||||
DevDiskImageHelper *devDiskImageHelper =
|
||||
new DevDiskImageHelper(m_device, this);
|
||||
connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this,
|
||||
[this, latitude, longitude, devDiskImageHelper](bool success) {
|
||||
if (devDiskImageHelper) {
|
||||
devDiskImageHelper->deleteLater();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
@@ -318,12 +329,6 @@ void VirtualLocation::onApplyClicked()
|
||||
|
||||
// Visual feedback
|
||||
m_applyButton->setText("Applied!");
|
||||
m_applyButton->setEnabled(false);
|
||||
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
m_applyButton->setText("Apply Settings");
|
||||
m_applyButton->setEnabled(true);
|
||||
});
|
||||
|
||||
bool locationSuccess = set_location(
|
||||
m_device->device,
|
||||
@@ -336,10 +341,148 @@ void VirtualLocation::onApplyClicked()
|
||||
QMessageBox::warning(this, "Error",
|
||||
"Failed to set location on device");
|
||||
} else {
|
||||
SettingsManager::sharedInstance()->saveRecentLocation(
|
||||
latitude, longitude);
|
||||
|
||||
QMessageBox::information(this, "Success",
|
||||
"Location applied successfully!");
|
||||
}
|
||||
});
|
||||
|
||||
helper->start();
|
||||
connect(
|
||||
devDiskImageHelper, &DevDiskImageHelper::destroyed, this,
|
||||
[this]() {
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
m_applyButton->setText("Apply Location");
|
||||
m_applyButton->setEnabled(true);
|
||||
});
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
devDiskImageHelper->start();
|
||||
}
|
||||
|
||||
void VirtualLocation::loadRecentLocations(QVBoxLayout *layout)
|
||||
{
|
||||
QList<QVariantMap> recentLocations =
|
||||
SettingsManager::sharedInstance()->getRecentLocations();
|
||||
|
||||
if (recentLocations.isEmpty()) {
|
||||
return; // Don't render anything if no recent locations
|
||||
}
|
||||
|
||||
layout->addItem(
|
||||
new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Fixed));
|
||||
|
||||
m_recentGroup = new QGroupBox("Recent Locations");
|
||||
layout->addWidget(m_recentGroup);
|
||||
|
||||
// A group box needs a layout to contain its children
|
||||
QVBoxLayout *groupBoxLayout = new QVBoxLayout(m_recentGroup);
|
||||
|
||||
QScrollArea *scrollArea = new QScrollArea();
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
groupBoxLayout->addWidget(scrollArea);
|
||||
|
||||
QWidget *scrollContent = new QWidget();
|
||||
scrollArea->setWidget(scrollContent);
|
||||
|
||||
// This layout is for the content widget
|
||||
QVBoxLayout *recentLayout = new QVBoxLayout(scrollContent);
|
||||
|
||||
addLocationButtons(recentLayout, recentLocations);
|
||||
}
|
||||
|
||||
void VirtualLocation::onRecentLocationClicked(double latitude, double longitude)
|
||||
{
|
||||
// Update input fields
|
||||
m_latitudeEdit->setText(QString::number(latitude, 'f', 6));
|
||||
m_longitudeEdit->setText(QString::number(longitude, 'f', 6));
|
||||
|
||||
// Update map
|
||||
updateMapFromInputs();
|
||||
|
||||
qDebug() << "Recent location clicked:" << latitude << "," << longitude;
|
||||
}
|
||||
|
||||
void VirtualLocation::refreshRecentLocations()
|
||||
{
|
||||
if (!m_recentGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the group box's layout
|
||||
QVBoxLayout *groupBoxLayout =
|
||||
qobject_cast<QVBoxLayout *>(m_recentGroup->layout());
|
||||
if (!groupBoxLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the scroll area from the group box layout
|
||||
QScrollArea *scrollArea = nullptr;
|
||||
if (groupBoxLayout->count() > 0) {
|
||||
scrollArea =
|
||||
qobject_cast<QScrollArea *>(groupBoxLayout->itemAt(0)->widget());
|
||||
}
|
||||
|
||||
if (!scrollArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the scroll content widget
|
||||
QWidget *scrollContent = scrollArea->widget();
|
||||
if (!scrollContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the content layout
|
||||
QVBoxLayout *recentLayout =
|
||||
qobject_cast<QVBoxLayout *>(scrollContent->layout());
|
||||
if (!recentLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all existing buttons
|
||||
QLayoutItem *item;
|
||||
while ((item = recentLayout->takeAt(0)) != nullptr) {
|
||||
if (item->widget()) {
|
||||
item->widget()->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
// Reload recent locations
|
||||
QList<QVariantMap> recentLocations =
|
||||
SettingsManager::sharedInstance()->getRecentLocations();
|
||||
|
||||
if (recentLocations.isEmpty()) {
|
||||
// Hide the group if no locations
|
||||
m_recentGroup->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the group if it was hidden
|
||||
m_recentGroup->show();
|
||||
|
||||
addLocationButtons(recentLayout, recentLocations);
|
||||
}
|
||||
|
||||
void VirtualLocation::addLocationButtons(QLayout *layout,
|
||||
QList<QVariantMap> recentLocations)
|
||||
{
|
||||
for (const QVariantMap &location : recentLocations) {
|
||||
double latitude = location["latitude"].toDouble();
|
||||
double longitude = location["longitude"].toDouble();
|
||||
|
||||
QPushButton *locationBtn =
|
||||
new QPushButton(QString("Lat: %1\nLon: %2")
|
||||
.arg(latitude, 0, 'f', 4)
|
||||
.arg(longitude, 0, 'f', 4));
|
||||
|
||||
connect(locationBtn, &QPushButton::clicked, this,
|
||||
[this, latitude, longitude]() {
|
||||
onRecentLocationClicked(latitude, longitude);
|
||||
});
|
||||
|
||||
layout->addWidget(locationBtn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,14 @@
|
||||
#ifndef VIRTUAL_LOCATION_H
|
||||
#define VIRTUAL_LOCATION_H
|
||||
|
||||
#include "devdiskimagehelper.h"
|
||||
#include "iDescriptor.h"
|
||||
#include <QGroupBox>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QQuickWidget>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class VirtualLocation : public QWidget
|
||||
@@ -47,8 +50,14 @@ private slots:
|
||||
void onMapCenterChanged();
|
||||
void onApplyClicked();
|
||||
void updateMapFromInputs();
|
||||
void onRecentLocationClicked(double latitude, double longitude);
|
||||
|
||||
private:
|
||||
void loadRecentLocations(QVBoxLayout *layout);
|
||||
void refreshRecentLocations();
|
||||
void addLocationButtons(QLayout *layout,
|
||||
QList<QVariantMap> recentLocations);
|
||||
|
||||
QQuickWidget *m_quickWidget;
|
||||
QLineEdit *m_latitudeEdit;
|
||||
QLineEdit *m_longitudeEdit;
|
||||
@@ -56,6 +65,8 @@ private:
|
||||
QTimer m_updateTimer;
|
||||
bool m_updatingFromInput = false;
|
||||
iDescriptorDevice *m_device;
|
||||
QVBoxLayout *m_rightLayout = nullptr;
|
||||
QGroupBox *m_recentGroup = nullptr;
|
||||
};
|
||||
|
||||
#endif // VIRTUAL_LOCATION_H
|
||||
@@ -37,6 +37,7 @@ WirelessGalleryImportWidget::WirelessGalleryImportWidget(QWidget *parent)
|
||||
m_loadingLabel(nullptr), m_tutorialLayout(nullptr)
|
||||
{
|
||||
setupUI();
|
||||
setMinimumSize(800, 600);
|
||||
QTimer::singleShot(100, this,
|
||||
&WirelessGalleryImportWidget::setupTutorialVideo);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user