feat: add DeviceImageWidget to display device mockups and wallpapers with current time
@@ -12,5 +12,28 @@
|
||||
<file>qml/MapView.qml</file>
|
||||
<file>resources/dump.js</file>
|
||||
<file>resources/iphone.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios4.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios5.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios6.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios7.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios8.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios9.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios10.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios11.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios12.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios13.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios14.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios15.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios16.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios17.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios18.png</file>
|
||||
<file>resources/ios-wallpapers/iphone-ios26.png</file>
|
||||
<file>resources/iphone-mockups/iphone-3.png</file>
|
||||
<file>resources/iphone-mockups/iphone-4.png</file>
|
||||
<file>resources/iphone-mockups/iphone-5.png</file>
|
||||
<file>resources/iphone-mockups/iphone-6.png</file>
|
||||
<file>resources/iphone-mockups/iphone-x.png</file>
|
||||
<file>resources/iphone-mockups/iphone-15.png</file>
|
||||
<file>resources/iphone-mockups/iphone-16.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 921 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
@@ -0,0 +1,297 @@
|
||||
#include "deviceimagewidget.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QMap>
|
||||
#include <QPainter>
|
||||
#include <QVBoxLayout>
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
|
||||
DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
: QWidget(parent), m_device(device)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_imageLabel = new ResponsiveQLabel(this);
|
||||
m_imageLabel->setMinimumWidth(200);
|
||||
m_imageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_imageLabel->setStyleSheet("background: transparent; border: none;");
|
||||
|
||||
layout->addWidget(m_imageLabel);
|
||||
|
||||
setupDeviceImage();
|
||||
m_timeUpdateTimer = new QTimer(this);
|
||||
connect(m_timeUpdateTimer, &QTimer::timeout, this,
|
||||
&DeviceImageWidget::updateTime);
|
||||
m_timeUpdateTimer->start(60000); // Update every minute
|
||||
|
||||
updateTime();
|
||||
}
|
||||
|
||||
DeviceImageWidget::~DeviceImageWidget()
|
||||
{
|
||||
if (m_timeUpdateTimer) {
|
||||
m_timeUpdateTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceImageWidget::setupDeviceImage()
|
||||
{
|
||||
m_mockupPath = getDeviceMockupPath();
|
||||
m_wallpaperPath = getWallpaperPath();
|
||||
|
||||
qDebug() << "Using mockup:" << m_mockupPath;
|
||||
qDebug() << "Using wallpaper:" << m_wallpaperPath;
|
||||
}
|
||||
|
||||
QString DeviceImageWidget::getDeviceMockupPath() const
|
||||
{
|
||||
QString displayName =
|
||||
QString::fromStdString(m_device->deviceInfo.productType);
|
||||
QString mockupName = getMockupNameFromDisplayName(displayName);
|
||||
|
||||
return QString(":/resources/iphone-mockups/iphone-%1.png").arg(mockupName);
|
||||
}
|
||||
|
||||
QString DeviceImageWidget::getWallpaperPath() const
|
||||
{
|
||||
int iosVersion = getIosVersionFromDevice();
|
||||
|
||||
// Map iOS version to available wallpapers
|
||||
QString wallpaperVersion;
|
||||
if (iosVersion >= 18) {
|
||||
wallpaperVersion = "ios18";
|
||||
} else if (iosVersion >= 17) {
|
||||
wallpaperVersion = "ios17";
|
||||
} else if (iosVersion >= 16) {
|
||||
wallpaperVersion = "ios16";
|
||||
} else if (iosVersion >= 15) {
|
||||
wallpaperVersion = "ios15";
|
||||
} else if (iosVersion >= 14) {
|
||||
wallpaperVersion = "ios14";
|
||||
} else if (iosVersion >= 13) {
|
||||
wallpaperVersion = "ios13";
|
||||
} else if (iosVersion >= 12) {
|
||||
wallpaperVersion = "ios12";
|
||||
} else if (iosVersion >= 11) {
|
||||
wallpaperVersion = "ios11";
|
||||
} else if (iosVersion >= 10) {
|
||||
wallpaperVersion = "ios10";
|
||||
} else if (iosVersion >= 9) {
|
||||
wallpaperVersion = "ios9";
|
||||
} else if (iosVersion >= 8) {
|
||||
wallpaperVersion = "ios8";
|
||||
} else if (iosVersion >= 7) {
|
||||
wallpaperVersion = "ios7";
|
||||
} else if (iosVersion >= 6) {
|
||||
wallpaperVersion = "ios6";
|
||||
} else if (iosVersion >= 5) {
|
||||
wallpaperVersion = "ios5";
|
||||
} else if (iosVersion >= 4) {
|
||||
wallpaperVersion = "ios4";
|
||||
} else {
|
||||
// Unknown version, use ios26 as fallback
|
||||
wallpaperVersion = "ios26";
|
||||
}
|
||||
|
||||
return QString(":/resources/ios-wallpapers/iphone-%1.png")
|
||||
.arg(wallpaperVersion);
|
||||
}
|
||||
|
||||
QString DeviceImageWidget::getMockupNameFromDisplayName(
|
||||
const QString &displayName) const
|
||||
{
|
||||
// Map device names to mockup files
|
||||
if (displayName.contains("iPhone 16", Qt::CaseInsensitive)) {
|
||||
return "16";
|
||||
} else if (displayName.contains("iPhone 15", Qt::CaseInsensitive)) {
|
||||
return "15";
|
||||
} else if (displayName.contains("iPhone X", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 11", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 12", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 13", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 14", Qt::CaseInsensitive)) {
|
||||
return "x";
|
||||
} else if (displayName.contains("iPhone 6", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 7", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone 8", Qt::CaseInsensitive)) {
|
||||
return "6";
|
||||
} else if (displayName.contains("iPhone 5", Qt::CaseInsensitive) ||
|
||||
displayName.contains("iPhone SE", Qt::CaseInsensitive)) {
|
||||
return "5";
|
||||
} else if (displayName.contains("iPhone 4", Qt::CaseInsensitive)) {
|
||||
return "4";
|
||||
} else if (displayName.contains("iPhone 3", Qt::CaseInsensitive)) {
|
||||
return "3";
|
||||
} else {
|
||||
// Unknown device, use iPhone X as default
|
||||
return "x";
|
||||
}
|
||||
}
|
||||
|
||||
int DeviceImageWidget::getIosVersionFromDevice() const
|
||||
{
|
||||
unsigned int version = idevice_get_device_version(m_device->device);
|
||||
|
||||
if (version > 0) {
|
||||
int majorVersion = (version >> 16) & 0xFF;
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
// Fallback: parse from productVersion string
|
||||
QString versionString =
|
||||
QString::fromStdString(m_device->deviceInfo.productVersion);
|
||||
QStringList parts = versionString.split('.');
|
||||
if (!parts.isEmpty()) {
|
||||
bool ok;
|
||||
int majorVersion = parts.first().toInt(&ok);
|
||||
if (ok) {
|
||||
return majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails, return unknown version (will use ios26 wallpaper)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
this method is only here to calculate the screen area
|
||||
so that wallpaper perfectly fits to the screen size
|
||||
it's costy so if you want to add a new mock run
|
||||
through this method qDebug the result and add it to createCompositeImage
|
||||
example : screenRect = QRect(152, 79, 195, 296);
|
||||
*/
|
||||
QRect DeviceImageWidget::findScreenArea(const QPixmap &mockup) const
|
||||
{
|
||||
QImage image = mockup.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
if (image.isNull()) {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
int width = image.width();
|
||||
int height = image.height();
|
||||
int centerX = width / 2;
|
||||
int centerY = height / 2;
|
||||
|
||||
if (qAlpha(image.pixel(centerX, centerY)) != 0) {
|
||||
qWarning() << "Cannot find screen area: center pixel is not "
|
||||
"transparent. Falling back to default.";
|
||||
return QRect(width * 0.1, height * 0.1, width * 0.8, height * 0.8);
|
||||
}
|
||||
|
||||
int left = centerX;
|
||||
int right = centerX;
|
||||
int top = centerY;
|
||||
int bottom = centerY;
|
||||
|
||||
// Scan left from center
|
||||
while (left > 0 && qAlpha(image.pixel(left, centerY)) == 0) {
|
||||
left--;
|
||||
}
|
||||
|
||||
// Scan right from center
|
||||
while (right < width - 1 && qAlpha(image.pixel(right, centerY)) == 0) {
|
||||
right++;
|
||||
}
|
||||
|
||||
// Scan up from center
|
||||
while (top > 0 && qAlpha(image.pixel(centerX, top)) == 0) {
|
||||
top--;
|
||||
}
|
||||
|
||||
// Scan down from center
|
||||
while (bottom < height - 1 && qAlpha(image.pixel(centerX, bottom)) == 0) {
|
||||
bottom++;
|
||||
}
|
||||
|
||||
// Add a small margin to avoid drawing over the bezel anti-aliasing
|
||||
int margin = 2;
|
||||
|
||||
return QRect(left + 1 + margin, top + 1 + margin,
|
||||
right - left - 2 - (margin * 2),
|
||||
bottom - top - 2 - (margin * 2));
|
||||
}
|
||||
|
||||
QPixmap DeviceImageWidget::createCompositeImage() const
|
||||
{
|
||||
QPixmap mockup(m_mockupPath);
|
||||
QPixmap wallpaper(m_wallpaperPath);
|
||||
|
||||
if (mockup.isNull()) {
|
||||
qWarning() << "Failed to load mockup:" << m_mockupPath;
|
||||
return QPixmap(":/resources/iphone.png"); // Fallback
|
||||
}
|
||||
|
||||
if (wallpaper.isNull()) {
|
||||
qWarning() << "Failed to load wallpaper:" << m_wallpaperPath;
|
||||
return mockup; // Return just the mockup
|
||||
}
|
||||
|
||||
// Start with the mockup as the base layer
|
||||
QPixmap composite = mockup.copy();
|
||||
QPainter painter(&composite);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// Use pre-calculated screen areas for optimal performance
|
||||
QRect screenRect;
|
||||
QString mockupName = getMockupNameFromDisplayName(
|
||||
QString::fromStdString(m_device->deviceInfo.productType));
|
||||
|
||||
if (mockupName == "3") {
|
||||
screenRect = QRect(152, 79, 195, 296);
|
||||
} else if (mockupName == "4") {
|
||||
screenRect = QRect(421, 188, 366, 534);
|
||||
} else if (mockupName == "5") {
|
||||
screenRect = QRect(34, 113, 290, 523);
|
||||
} else if (mockupName == "6") {
|
||||
screenRect = QRect(75, 355, 1265, 2256);
|
||||
} else if (mockupName == "x") {
|
||||
screenRect = QRect(252, 436, 2375, 4989);
|
||||
} else if (mockupName == "15") {
|
||||
screenRect = QRect(22, 56, 323, 674);
|
||||
} else if (mockupName == "16") {
|
||||
screenRect = QRect(24, 61, 319, 668);
|
||||
} else {
|
||||
// Fallback for unknown devices
|
||||
screenRect = QRect(mockup.width() * 0.12, mockup.height() * 0.08,
|
||||
mockup.width() * 0.76, mockup.height() * 0.84);
|
||||
}
|
||||
|
||||
// Draw wallpaper BEHIND the mockup (into the screen area)
|
||||
QPixmap scaledWallpaper = wallpaper.scaled(
|
||||
screenRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
painter.drawPixmap(screenRect, scaledWallpaper);
|
||||
|
||||
// Draw current time in the center of the screen
|
||||
QString currentTime = QDateTime::currentDateTime().toString("hh:mm");
|
||||
|
||||
// Setup text rendering with better font sizing
|
||||
QFont timeFont;
|
||||
timeFont.setFamily("SF Pro Display, Helvetica, Arial");
|
||||
|
||||
// Scale font size based on screen dimensions
|
||||
int fontSize = screenRect.width() / 5;
|
||||
timeFont.setPointSize(fontSize);
|
||||
timeFont.setWeight(QFont::Light);
|
||||
|
||||
painter.setFont(timeFont);
|
||||
|
||||
// Draw text shadow for better readability
|
||||
painter.setPen(QColor(0, 0, 0, 150));
|
||||
painter.drawText(screenRect.adjusted(2, 2, 2, 2), Qt::AlignCenter,
|
||||
currentTime);
|
||||
|
||||
// Draw main text - perfectly centered in the screen area
|
||||
painter.setPen(QColor(255, 255, 255, 255));
|
||||
painter.drawText(screenRect, Qt::AlignCenter, currentTime);
|
||||
|
||||
painter.end();
|
||||
return composite;
|
||||
}
|
||||
|
||||
void DeviceImageWidget::updateTime()
|
||||
{
|
||||
QPixmap composite = createCompositeImage();
|
||||
m_imageLabel->setPixmap(composite);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef DEVICEIMAGEWIDGET_H
|
||||
#define DEVICEIMAGEWIDGET_H
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include "responsiveqlabel.h"
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
class DeviceImageWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeviceImageWidget(iDescriptorDevice *device,
|
||||
QWidget *parent = nullptr);
|
||||
~DeviceImageWidget();
|
||||
|
||||
private slots:
|
||||
void updateTime();
|
||||
|
||||
private:
|
||||
void setupDeviceImage();
|
||||
QString getDeviceMockupPath() const;
|
||||
QString getWallpaperPath() const;
|
||||
QString getMockupNameFromDisplayName(const QString &displayName) const;
|
||||
int getIosVersionFromDevice() const;
|
||||
QPixmap createCompositeImage() const;
|
||||
QRect findScreenArea(const QPixmap &mockup) const;
|
||||
|
||||
iDescriptorDevice *m_device;
|
||||
ResponsiveQLabel *m_imageLabel;
|
||||
QTimer *m_timeUpdateTimer;
|
||||
|
||||
QString m_mockupPath;
|
||||
QString m_wallpaperPath;
|
||||
};
|
||||
|
||||
#endif // DEVICEIMAGEWIDGET_H
|
||||
@@ -35,16 +35,13 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
|
||||
// Left side container for image and actions
|
||||
QWidget *leftContainer = new QWidget();
|
||||
// leftContainer->setStyleSheet("margin-left: 100px");
|
||||
QVBoxLayout *leftLayout = new QVBoxLayout(leftContainer);
|
||||
leftLayout->setContentsMargins(0, 0, 0, 0);
|
||||
leftLayout->setSpacing(1);
|
||||
|
||||
// Create responsive image label
|
||||
m_deviceImageLabel = new ResponsiveQLabel(this);
|
||||
m_deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png"));
|
||||
m_deviceImageLabel->setMinimumWidth(200);
|
||||
m_deviceImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_deviceImageLabel->setStyleSheet("background: transparent; border: none;");
|
||||
// Create responsive device image widget
|
||||
m_deviceImageWidget = new DeviceImageWidget(device, this);
|
||||
|
||||
// Actions group box
|
||||
QWidget *actionsWidget = new QWidget();
|
||||
@@ -80,7 +77,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
actionsLayout->addWidget(recoveryBtn);
|
||||
|
||||
leftLayout->addStretch();
|
||||
leftLayout->addWidget(m_deviceImageLabel);
|
||||
leftLayout->addWidget(m_deviceImageWidget);
|
||||
leftLayout->addWidget(actionsWidget, 0, Qt::AlignCenter);
|
||||
leftLayout->addStretch();
|
||||
|
||||
@@ -329,29 +326,6 @@ void DeviceInfoWidget::onBatteryMoreClicked()
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
QPixmap DeviceInfoWidget::getDeviceIcon(const std::string &productType)
|
||||
{
|
||||
// Create a simple colored icon based on device type
|
||||
QPixmap icon(16, 16);
|
||||
icon.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&icon);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
if (productType.find("iPhone") != std::string::npos) {
|
||||
painter.setBrush(QColor(0, 122, 255)); // iOS blue
|
||||
painter.drawEllipse(2, 2, 12, 12);
|
||||
} else if (productType.find("iPad") != std::string::npos) {
|
||||
painter.setBrush(QColor(255, 149, 0)); // Orange
|
||||
painter.drawRect(2, 2, 12, 12);
|
||||
} else {
|
||||
painter.setBrush(QColor(128, 128, 128)); // Gray for unknown
|
||||
painter.drawEllipse(2, 2, 12, 12);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
void DeviceInfoWidget::updateBatteryInfo()
|
||||
{
|
||||
qDebug() << "Updating battery info...";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef DEVICEINFOWIDGET_H
|
||||
#define DEVICEINFOWIDGET_H
|
||||
#include "batterywidget.h"
|
||||
#include "deviceimagewidget.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "responsiveqlabel.h"
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
@@ -18,7 +18,6 @@ private slots:
|
||||
void onBatteryMoreClicked();
|
||||
|
||||
private:
|
||||
QPixmap getDeviceIcon(const std::string &productType);
|
||||
iDescriptorDevice *m_device;
|
||||
QTimer *m_updateTimer;
|
||||
void updateBatteryInfo();
|
||||
@@ -28,7 +27,7 @@ private:
|
||||
BatteryWidget *m_batteryWidget;
|
||||
QLabel *m_lightningIconLabel;
|
||||
|
||||
ResponsiveQLabel *m_deviceImageLabel = nullptr;
|
||||
DeviceImageWidget *m_deviceImageWidget;
|
||||
};
|
||||
|
||||
#endif // DEVICEINFOWIDGET_H
|
||||
|
||||
@@ -22,8 +22,6 @@ public:
|
||||
static void shutdownDevice(iDescriptorDevice *device);
|
||||
static void _enterRecoveryMode(iDescriptorDevice *device);
|
||||
private slots:
|
||||
void onDeviceAdded();
|
||||
void onDeviceRemoved();
|
||||
void onDeviceSelectionChanged();
|
||||
void onToolboxClicked(iDescriptorTool tool);
|
||||
|
||||
|
||||