refactor ui

- Implemented CustomTab class with notification support and custom painting.
- Created CustomTabWidget to manage multiple tabs with a stacked widget layout.
- Integrated glider animation for smooth tab transitions.
- Updated DeviceInfoWidget to improve layout and visual appearance with shadows.
- Refactored DeviceMenuWidget to use QStackedWidget instead of QTabWidget for better flexibility.
- Enhanced main window setup with custom tab widget and improved device management UI.
- Added macOS specific window styling for a more native look.
- Improved ToolboxWidget layout and styling for better user experience.
This commit is contained in:
uncor3
2025-09-29 04:29:13 -07:00
parent a4ac36cc5a
commit 6b9fdd9299
20 changed files with 725 additions and 245 deletions
+3
View File
@@ -140,6 +140,9 @@ file(GLOB PROJECT_SOURCES
src/*.cpp
src/core/helpers/*.cpp
src/core/services/*.cpp
src/platform/*.cpp
src/platform/*.mm
src/platform/*.m
src/*.h
src/*.ui
resources.qrc
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+2
View File
@@ -2,6 +2,8 @@
<qresource prefix="/">
<file>icons/ArrowMoveDownRight.svg</file>
<file>icons/video-x-generic.png</file>
<file>icons/MdiLightningBolt.png</file>
<file>icons/MingcuteSettings7Line.png</file>
<file>qml/MapView.qml</file>
<file>resources/dump.js</file>
<file>resources/iphone.png</file>
+5
View File
@@ -153,6 +153,11 @@ void AppContext::instanceRemoveDevice(QString _udid)
// return true;
}
int AppContext::getConnectedDeviceCount() const
{
return m_devices.size() + m_recoveryDevices.size();
}
void AppContext::removeDevice(QString _udid)
{
+1
View File
@@ -26,6 +26,7 @@ public:
QList<RecoveryDeviceInfo *> getAllRecoveryDevices();
~AppContext();
void instanceRemoveDevice(QString _udid);
int getConnectedDeviceCount() const;
private:
QMap<std::string, iDescriptorDevice *> m_devices;
+3 -12
View File
@@ -10,8 +10,8 @@
BatteryWidget::BatteryWidget(float value, bool isCharging, QWidget *parent)
: QWidget(parent), m_value(value), m_isCharging(isCharging)
{
setMinimumSize(50, 40);
setMaximumSize(60, 40);
setMinimumSize(30, 30);
setMaximumSize(40, 40);
}
void BatteryWidget::resizeEvent(QResizeEvent *)
@@ -111,7 +111,7 @@ void BatteryWidget::paintEvent(QPaintEvent *)
pen.setColor(Qt::white);
painter.setPen(pen);
QFont textFont = QFont();
textFont.setPixelSize(widgetFrame.height() / 2);
textFont.setPixelSize(widgetFrame.height() / 1.65);
painter.setFont(textFont);
QFontMetrics fm(textFont);
QString percentageLevelString = QString("%1%").arg(m_value);
@@ -121,13 +121,4 @@ void BatteryWidget::paintEvent(QPaintEvent *)
QPointF textPosition = QPointF(widgetFrame.center().x() - textWidth / 2,
widgetFrame.center().y() + textHeight / 3);
painter.drawText(textPosition, percentageLevelString);
float chargerSize = widgetFrame.height() / 2;
// if (isCharging) {
// QPixmap pixmap(":/img/charge.png");
// painter.drawPixmap(widgetFrame.center().x() - chargerSize * 1.5,
// widgetFrame.top() + chargerSize / 2, chargerSize,
// chargerSize, pixmap);
// }
}
+329
View File
@@ -0,0 +1,329 @@
#include "customtabwidget.h"
#include <QEasingCurve>
#include <QGraphicsDropShadowEffect>
#include <QMainWindow>
#include <QPainter>
#include <QStyleOption>
#include <QTimer>
// CustomTab implementation
CustomTab::CustomTab(const QString &text, QWidget *parent)
: QPushButton(text, parent), m_notificationLabel(nullptr),
m_notificationCount(0)
{
setCheckable(true);
setFixedHeight(54);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// Set up notification label
m_notificationLabel = new QLabel(this);
m_notificationLabel->setAlignment(Qt::AlignCenter);
m_notificationLabel->hide();
m_notificationLabel->setStyleSheet("QLabel {"
" background-color: #e6eef9;"
" border-radius: 16px;"
" color: #333;"
" font-weight: 500;"
" min-width: 32px;"
" min-height: 32px;"
" max-width: 32px;"
" max-height: 32px;"
"}");
updateNotificationDisplay();
}
void CustomTab::setNotificationCount(int count)
{
m_notificationCount = count;
updateNotificationDisplay();
}
void CustomTab::setIcon(const QIcon &icon)
{
QPushButton::setIcon(icon);
setIconSize(QSize(20, 20));
}
void CustomTab::updateNotificationDisplay()
{
if (m_notificationCount > 0) {
m_notificationLabel->setText(QString::number(m_notificationCount));
m_notificationLabel->show();
// Position notification label to the right of the text
QFontMetrics fm(font());
int textWidth = fm.horizontalAdvance(text());
int iconWidth = iconSize().width();
int totalContentWidth = (iconWidth > 0 ? iconWidth + 8 : 0) + textWidth;
int x = (width() - totalContentWidth) / 2 + totalContentWidth + 12;
int y = (height() - 32) / 2;
m_notificationLabel->setGeometry(x, y, 32, 32);
} else {
m_notificationLabel->hide();
}
}
void CustomTab::paintEvent(QPaintEvent *event)
{
QPushButton::paintEvent(event);
updateNotificationDisplay();
// Update notification label style based on checked state
if (isChecked()) {
m_notificationLabel->setStyleSheet("QLabel {"
" background-color: #185ee0;"
" border-radius: 16px;"
" color: white;"
" font-weight: 500;"
" min-width: 32px;"
" min-height: 32px;"
" max-width: 32px;"
" max-height: 32px;"
"}");
} else {
m_notificationLabel->setStyleSheet("QLabel {"
" background-color: #e6eef9;"
" border-radius: 16px;"
" color: #333;"
" font-weight: 500;"
" min-width: 32px;"
" min-height: 32px;"
" max-width: 32px;"
" max-height: 32px;"
"}");
}
}
// CustomTabWidget implementation
CustomTabWidget::CustomTabWidget(QWidget *parent)
: QWidget(parent), m_currentIndex(0)
{
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->setSpacing(0);
// Create tab bar container
m_tabBar = new QWidget();
m_tabBar->setFixedHeight(70); // 54px height + 16px padding
m_tabLayout = new QHBoxLayout(m_tabBar);
// m_tabLayout->setContentsMargins(12, 8, 12, 8);
m_tabLayout->setSpacing(0);
// Style the tab bar
m_tabBar->setStyleSheet("QWidget {"
// " background-color: white;"
" border-radius: 35px;"
"}");
// Add drop shadow effect
QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
shadow->setBlurRadius(20);
shadow->setColor(QColor(24, 94, 224, 38)); // rgba(24, 94, 224, 0.15)
shadow->setOffset(0, 6);
m_tabBar->setGraphicsEffect(shadow);
m_buttonGroup = new QButtonGroup(this);
m_buttonGroup->setExclusive(true);
// Create stacked widget for content
m_stackedWidget = new QStackedWidget();
// Add widgets to layout
m_mainLayout->addWidget(m_tabBar);
m_mainLayout->addWidget(m_stackedWidget, 1);
setupGlider();
}
void CustomTabWidget::setupGlider()
{
m_glider = new QWidget(m_tabBar);
m_glider->setStyleSheet("QWidget {"
" background-color: #185ee0;"
" border-radius: 1px;"
"}");
// Set initial size - will be updated in animateGlider
m_glider->setFixedSize(100, 2); // 2px height for bottom border effect
m_glider->lower(); // Make sure glider is behind tabs
m_gliderAnimation = new QPropertyAnimation(m_glider, "pos");
m_gliderAnimation->setDuration(250);
m_gliderAnimation->setEasingCurve(QEasingCurve::OutCubic);
}
int CustomTabWidget::addTab(QWidget *widget, const QString &label)
{
return addTab(widget, QIcon(), label);
}
int CustomTabWidget::addTab(QWidget *widget, const QIcon &icon,
const QString &label)
{
CustomTab *tab = new CustomTab(label, m_tabBar);
if (!icon.isNull()) {
tab->setIcon(icon);
}
connect(tab, &CustomTab::clicked, this, &CustomTabWidget::onTabClicked);
int index = m_tabs.count();
m_tabs.append(tab);
m_widgets.append(widget);
m_tabLayout->addWidget(tab);
m_stackedWidget->addWidget(widget);
m_buttonGroup->addButton(tab, index);
// Set first tab as checked by default
if (index == 0) {
tab->setChecked(true);
}
return index;
}
void CustomTabWidget::setCurrentIndex(int index)
{
if (index < 0 || index >= m_tabs.count() || index == m_currentIndex) {
return;
}
m_currentIndex = index;
m_tabs[index]->setChecked(true);
m_stackedWidget->setCurrentIndex(index);
updateTabStyles();
animateGlider(index);
emit currentChanged(index);
}
void CustomTabWidget::finalizeStyles()
{
updateTabStyles();
// Position glider for first tab
QTimer::singleShot(0, [this]() { animateGlider(0); });
}
int CustomTabWidget::currentIndex() const { return m_currentIndex; }
QWidget *CustomTabWidget::widget(int index) const
{
if (index < 0 || index >= m_widgets.count()) {
return nullptr;
}
return m_widgets[index];
}
void CustomTabWidget::setTabNotification(int index, int count)
{
if (index >= 0 && index < m_tabs.count()) {
m_tabs[index]->setNotificationCount(count);
}
}
void CustomTabWidget::onTabClicked()
{
CustomTab *clickedTab = qobject_cast<CustomTab *>(sender());
if (!clickedTab)
return;
int index = m_tabs.indexOf(clickedTab);
if (index != -1) {
setCurrentIndex(index);
}
}
void CustomTabWidget::animateGlider(int index)
{
if (index < 0 || index >= m_tabs.count())
return;
CustomTab *targetTab = m_tabs[index];
if (!targetTab)
return;
// Get the actual position and size of the target tab
QPoint targetTabPos = targetTab->pos();
QSize targetTabSize = targetTab->size();
// Set glider width to match tab width and height to 2px for bottom border
m_glider->setFixedSize(targetTabSize.width(), 2);
// Position glider at the bottom of the target tab
int targetX = targetTabPos.x();
int targetY =
targetTabPos.y() + targetTabSize.height() - 2; // Position at bottom
m_gliderAnimation->stop();
m_gliderAnimation->setStartValue(m_glider->pos());
m_gliderAnimation->setEndValue(QPoint(targetX, targetY));
m_gliderAnimation->start();
}
void CustomTabWidget::updateTabStyles()
{
for (int i = 0; i < m_tabs.count(); ++i) {
CustomTab *tab = m_tabs[i];
if (tab->isChecked()) {
tab->setStyleSheet("CustomTab {"
" color: #185ee0;"
" font-weight: 500;"
" font-size: 20px;"
" border: none;"
" border-radius: 27px;"
" background-color: transparent;"
"}"
"CustomTab:hover {"
" background-color: transparent;"
"}");
} else {
tab->setStyleSheet("CustomTab {"
" color: #666;"
" font-weight: 500;"
" font-size: 20px;"
" border: none;"
" border-radius: 27px;"
" background-color: transparent;"
"}"
"CustomTab:hover {"
" color: #185ee0;"
" background-color: transparent;"
"}");
}
}
}
void CustomTabWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
// Update glider position when widget is resized
if (m_currentIndex >= 0 && m_currentIndex < m_tabs.count()) {
// Use a timer to ensure layout has been updated
QTimer::singleShot(0, [this]() { animateGlider(m_currentIndex); });
}
}
// #ifdef Q_OS_MAC
// void CustomTabWidget::ensureTitlebarIntegration()
// {
// // Ensure the tab bar maintains the correct height and margins for
// titlebar integration m_tabBar->setFixedHeight(98); // 70px + 28px
// titlebar height m_tabLayout->setContentsMargins(12, 36, 12, 8); // Add
// top margin for titlebar
// // Ensure the parent window attribute is maintained
// if (QMainWindow *mainWindow = qobject_cast<QMainWindow*>(window())) {
// mainWindow->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea,
// false);
// }
// // Update glider position after titlebar integration changes
// if (m_currentIndex >= 0 && m_currentIndex < m_tabs.count()) {
// QTimer::singleShot(0, [this]() {
// animateGlider(m_currentIndex);
// });
// }
// }
// #endif
+72
View File
@@ -0,0 +1,72 @@
#ifndef CUSTOMTABWIDGET_H
#define CUSTOMTABWIDGET_H
#include <QButtonGroup>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QWidget>
class CustomTab : public QPushButton
{
Q_OBJECT
public:
explicit CustomTab(const QString &text, QWidget *parent = nullptr);
void setNotificationCount(int count);
void setIcon(const QIcon &icon);
private:
QLabel *m_notificationLabel;
int m_notificationCount;
void updateNotificationDisplay();
protected:
void paintEvent(QPaintEvent *event) override;
};
class CustomTabWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomTabWidget(QWidget *parent = nullptr);
void finalizeStyles();
int addTab(QWidget *widget, const QString &label);
int addTab(QWidget *widget, const QIcon &icon, const QString &label);
void setCurrentIndex(int index);
int currentIndex() const;
QWidget *widget(int index) const;
void setTabNotification(int index, int count);
signals:
void currentChanged(int index);
private slots:
void onTabClicked();
protected:
void resizeEvent(QResizeEvent *event) override;
private:
QHBoxLayout *m_tabLayout;
QVBoxLayout *m_mainLayout;
QWidget *m_tabBar;
QStackedWidget *m_stackedWidget;
QButtonGroup *m_buttonGroup;
QWidget *m_glider;
QPropertyAnimation *m_gliderAnimation;
QList<CustomTab *> m_tabs;
QList<QWidget *> m_widgets;
int m_currentIndex;
void setupGlider();
void animateGlider(int index);
void updateTabStyles();
};
#endif // CUSTOMTABWIDGET_H
+95 -27
View File
@@ -5,6 +5,7 @@
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include <QDebug>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsPixmapItem>
#include <QGraphicsView>
#include <QGridLayout>
@@ -75,30 +76,50 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
QString::number(device->deviceInfo.diskInfo.totalDiskCapacity /
(1000 * 1000 * 1000)) +
" GB");
diskCapacityLabel->setAttribute(Qt::WA_StyledBackground, true);
diskCapacityLabel->setStyleSheet("background-color: rgba(0, 255, 30, 0.12);"
"padding: 4px;"
"border-radius: 4px;");
m_chargingStatusLabel =
new QLabel(device->deviceInfo.batteryInfo.isCharging ? "Charging"
: "Not Charging");
m_chargingStatusLabel->setStyleSheet("font-size: 1rem;");
m_chargingStatusLabel->setStyleSheet(
device->deviceInfo.batteryInfo.isCharging ? "color: green;"
: "color: white;");
m_chargingWattsLabel =
new QLabel(QString::number(device->deviceInfo.batteryInfo.watts) + "W");
m_cableTypeLabel =
new QLabel(device->deviceInfo.batteryInfo.usbConnectionType ==
BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C");
// Create the layout without a parent widget
QHBoxLayout *chargingLayout = new QHBoxLayout();
chargingLayout->setContentsMargins(0, 0, 0, 0);
chargingLayout->setSpacing(5);
// Create icon label
m_lightningIconLabel = new QLabel();
QPixmap lightningIcon(":/icons/MdiLightningBolt.png");
QPixmap scaledIcon = lightningIcon.scaled(16, 16, Qt::KeepAspectRatio,
Qt::SmoothTransformation);
m_lightningIconLabel->setPixmap(scaledIcon);
m_batteryWidget =
new BatteryWidget(device->deviceInfo.batteryInfo.currentBatteryLevel,
device->deviceInfo.batteryInfo.isCharging, this);
// Add the widgets to the new layout
chargingLayout->addWidget(m_chargingStatusLabel);
chargingLayout->addWidget(m_lightningIconLabel);
chargingLayout->addWidget(m_batteryWidget);
m_chargingWattsWithCableTypeLabel = new QLabel(
QString::number(device->deviceInfo.batteryInfo.watts) + "W" + "/" +
(device->deviceInfo.batteryInfo.usbConnectionType ==
BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C"));
headerLayout->addWidget(devProductType);
headerLayout->addWidget(diskCapacityLabel);
headerLayout->addWidget(m_chargingStatusLabel);
headerLayout->addWidget(m_batteryWidget);
headerLayout->addWidget(m_chargingWattsLabel);
headerLayout->addWidget(m_cableTypeLabel);
headerLayout->addLayout(chargingLayout);
headerLayout->addWidget(m_chargingWattsWithCableTypeLabel);
infoLayout->addWidget(headerWidget);
// add spacer
@@ -107,14 +128,47 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
// Add maximum stretch between header and grid
infoLayout->addStretch();
// Grid for device details
// --- Neumorphic Grid Widget ---
// 1. Create a container for the shadows
QWidget *shadowContainer = new QWidget();
// The container must be transparent to not hide the main window background
shadowContainer->setStyleSheet("background: transparent;");
// Use a layout to make the gridWidget fill the container
QVBoxLayout *shadowLayout = new QVBoxLayout(shadowContainer);
shadowLayout->setContentsMargins(15, 15, 15,
15); // Margins to make space for shadows
// 2. Create the dark (bottom-right) shadow and apply to the container
QGraphicsDropShadowEffect *darkShadow = new QGraphicsDropShadowEffect();
darkShadow->setBlurRadius(30);
darkShadow->setColor(QColor(0, 0, 0, 70)); // Dark, semi-transparent color
darkShadow->setOffset(0, 0);
shadowContainer->setGraphicsEffect(darkShadow);
// 3. Create the grid widget (the main content)
QWidget *gridWidget = new QWidget();
gridWidget->setObjectName("infoGrid");
gridWidget->setStyleSheet("QWidget#infoGrid { "
" border: 1px solid #ccc; "
" border-radius: 6px; "
"}");
QGridLayout *gridLayout = new QGridLayout();
// Set a background color that matches the main window, with rounded corners
gridWidget->setStyleSheet(
"QWidget#infoGrid {"
" background-color: #2e2e2e;" // Match your window background
" border-radius: 8px;"
"}");
// 4. Create the light (top-left) shadow and apply to the grid widget
QGraphicsDropShadowEffect *lightShadow = new QGraphicsDropShadowEffect();
lightShadow->setBlurRadius(30);
lightShadow->setColor(
QColor(255, 255, 255, 40)); // Light, semi-transparent color
lightShadow->setOffset(0, 0);
gridWidget->setGraphicsEffect(lightShadow);
// Add gridWidget to the container's layout
shadowLayout->addWidget(gridWidget);
QGridLayout *gridLayout =
new QGridLayout(gridWidget); // Set layout on gridWidget
gridLayout->setSpacing(8);
gridLayout->setColumnStretch(1, 1); // Allow value column to stretch
gridLayout->setColumnStretch(
@@ -228,7 +282,8 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
}
}
infoLayout->addWidget(gridWidget);
infoLayout->addWidget(
shadowContainer); // Add the container to the main layout
// infoLayout->addStretch(); // Pushes footer to the bottom
// Footer
@@ -310,14 +365,27 @@ void DeviceInfoWidget::updateBatteryInfo()
else
parseDeviceBattery(ioreg, d);
/*UI*/
m_chargingStatusLabel->setText(d.batteryInfo.isCharging ? "Charging"
: "Not Charging");
m_chargingWattsLabel->setText(QString::number(d.batteryInfo.watts) + "W");
m_cableTypeLabel->setText(d.batteryInfo.usbConnectionType ==
BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C");
updateChargingStatusIcon();
m_chargingWattsWithCableTypeLabel->setText(
QString::number(d.batteryInfo.watts) + "W" + "/" +
(d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C"));
m_batteryWidget->updateContext(d.batteryInfo.isCharging,
d.batteryInfo.currentBatteryLevel);
}
void DeviceInfoWidget::updateChargingStatusIcon()
{
if (m_device->deviceInfo.batteryInfo.isCharging) {
m_chargingStatusLabel->setText("Charging");
m_chargingStatusLabel->setStyleSheet("color: green;");
m_lightningIconLabel->show();
} else {
m_chargingStatusLabel->setText("Not Charging");
m_chargingStatusLabel->setStyleSheet("color: white;");
m_lightningIconLabel->hide();
}
}
+3 -2
View File
@@ -21,10 +21,11 @@ private:
iDescriptorDevice *m_device;
QTimer *m_updateTimer;
void updateBatteryInfo();
void updateChargingStatusIcon();
QLabel *m_chargingStatusLabel;
QLabel *m_chargingWattsLabel;
QLabel *m_cableTypeLabel;
QLabel *m_chargingWattsWithCableTypeLabel;
BatteryWidget *m_batteryWidget;
QLabel *m_lightningIconLabel;
};
#endif // DEVICEINFOWIDGET_H
+33 -24
View File
@@ -5,37 +5,46 @@
#include "iDescriptor.h"
#include "installedappswidget.h"
#include <QDebug>
#include <QTabWidget>
#include <QStackedWidget>
#include <QVBoxLayout>
DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
: QWidget{parent}, device(device)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
QWidget *centralWidget = new QWidget(this);
tabWidget = new QTabWidget(this);
tabWidget->tabBar()->hide();
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
mainLayout->addWidget(tabWidget);
stackedWidget = new QStackedWidget(this);
mainLayout->addWidget(stackedWidget);
FileExplorerWidget *explorer = new FileExplorerWidget(device, this);
explorer->setMinimumHeight(300);
// Create and add widgets to the stacked widget
DeviceInfoWidget *deviceInfoWidget = new DeviceInfoWidget(device, this);
InstalledAppsWidget *installedAppsWidget =
new InstalledAppsWidget(device, this);
GalleryWidget *galleryWidget = new GalleryWidget(device, this);
FileExplorerWidget *fileExplorerWidget =
new FileExplorerWidget(device, this);
GalleryWidget *gallery = new GalleryWidget(device, this);
gallery->setMinimumHeight(300);
setLayout(mainLayout);
// Set minimum heights
galleryWidget->setMinimumHeight(300);
fileExplorerWidget->setMinimumHeight(300);
tabWidget->addTab(new DeviceInfoWidget(device, this), "");
tabWidget->addTab(new InstalledAppsWidget(device, this), "");
unsigned int galleryIndex = tabWidget->addTab(gallery, "");
tabWidget->addTab(explorer, "");
// Add widgets to stack (index 0, 1, 2, 3)
stackedWidget->addWidget(deviceInfoWidget); // Index 0 - Info
stackedWidget->addWidget(installedAppsWidget); // Index 1 - Apps
stackedWidget->addWidget(galleryWidget); // Index 2 - Gallery
stackedWidget->addWidget(fileExplorerWidget); // Index 3 - Files
// TODO : one time ?
connect(tabWidget, &QTabWidget::currentChanged, this,
[this, galleryIndex, gallery](int index) {
if (index == galleryIndex) {
// Set default to Info tab
stackedWidget->setCurrentIndex(0);
// Connect to current changed signal for lazy loading
connect(stackedWidget, &QStackedWidget::currentChanged, this,
[this, galleryWidget](int index) {
if (index == 2) { // Gallery tab
qDebug() << "Switched to Gallery tab";
gallery->load();
galleryWidget->load();
}
});
}
@@ -43,13 +52,13 @@ DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
void DeviceMenuWidget::switchToTab(const QString &tabName)
{
if (tabName == "Info") {
tabWidget->setCurrentIndex(0);
stackedWidget->setCurrentIndex(0);
} else if (tabName == "Apps") {
tabWidget->setCurrentIndex(1);
stackedWidget->setCurrentIndex(1);
} else if (tabName == "Gallery") {
tabWidget->setCurrentIndex(2);
stackedWidget->setCurrentIndex(2);
} else if (tabName == "Files") {
tabWidget->setCurrentIndex(3);
stackedWidget->setCurrentIndex(3);
} else {
qDebug() << "Tab not found:" << tabName;
}
+3 -3
View File
@@ -1,7 +1,7 @@
#ifndef DEVICEMENUWIDGET_H
#define DEVICEMENUWIDGET_H
#include "iDescriptor.h"
#include <QTabWidget>
#include <QStackedWidget>
#include <QWidget>
class DeviceMenuWidget : public QWidget
{
@@ -12,8 +12,8 @@ public:
void switchToTab(const QString &tabName);
// ~DeviceMenuWidget();
private:
QTabWidget *tabWidget; // Pointer to the tab widget
iDescriptorDevice *device; // Pointer to the iDescriptor device
QStackedWidget *stackedWidget; // Pointer to the stacked widget
iDescriptorDevice *device; // Pointer to the iDescriptor device
signals:
};
+1 -1
View File
@@ -218,7 +218,7 @@ const std::string &DeviceSidebarItem::getDeviceUuid() const { return m_uuid; }
DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) : QWidget(parent)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(0);
// Create scroll area
+7 -1
View File
@@ -1,5 +1,7 @@
#pragma once
#include <QGraphicsView>
#include <QMainWindow>
// A custom QGraphicsView that keeps the content fitted with aspect ratio on
// resize
class ResponsiveGraphicsView : public QGraphicsView
@@ -18,4 +20,8 @@ protected:
}
QGraphicsView::resizeEvent(event);
}
};
};
#ifdef Q_OS_MAC
void setupMacOSWindow(QMainWindow *window);
#endif
+103 -22
View File
@@ -1,5 +1,6 @@
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "customtabwidget.h"
#include "detailwindow.h"
#include "settingswidget.h"
#include <QDialog>
@@ -7,6 +8,7 @@
#include <QGraphicsSvgItem>
#include <QMessageBox>
#include <QSvgRenderer>
#include <QTimer>
#include <QtSvg>
#include <libimobiledevice/libimobiledevice.h>
@@ -15,6 +17,7 @@
#include "appswidget.h"
#include "devicemanagerwidget.h"
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "libirecovery.h"
#include "toolboxwidget.h"
@@ -24,6 +27,7 @@
#include <QPushButton>
#include <QScrollArea>
#include <QStack>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QWidget>
@@ -33,6 +37,10 @@
#include "fileexplorerwidget.h"
#include "jailbrokenwidget.h"
#include "recoverydeviceinfowidget.h"
#include <QApplication>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <libusb-1.0/libusb.h>
#include <stdio.h>
#include <stdlib.h>
@@ -116,16 +124,67 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("iDescriptor");
// Create custom tab widget
m_customTabWidget = new CustomTabWidget(this);
m_customTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea,
false);
setContentsMargins(0, 0, 0, 0);
#ifdef Q_OS_MAC
setupMacOSWindow(this);
setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
#endif
setCentralWidget(m_customTabWidget);
// Create device manager and stacked widget for main tab
m_mainStackedWidget = new QStackedWidget();
// No devices page
QWidget *noDevicesPage = new QWidget();
QVBoxLayout *noDeviceLayout = new QVBoxLayout(noDevicesPage);
noDeviceLayout->addStretch();
QHBoxLayout *labelLayout = new QHBoxLayout();
labelLayout->addStretch();
QLabel *noDeviceLabel = new QLabel("No devices detected");
noDeviceLabel->setAlignment(Qt::AlignCenter);
labelLayout->addWidget(noDeviceLabel);
labelLayout->addStretch();
noDeviceLayout->addLayout(labelLayout);
noDeviceLayout->addStretch();
m_deviceManager = new DeviceManagerWidget(this);
ui->stackedWidget->insertWidget(1, m_deviceManager);
m_mainStackedWidget->addWidget(noDevicesPage);
m_mainStackedWidget->addWidget(m_deviceManager);
connect(m_deviceManager, &DeviceManagerWidget::updateNoDevicesConnected,
this, &MainWindow::updateNoDevicesConnected);
// Add tabs with icons
QIcon deviceIcon(":/icons/MdiLightningBolt.png");
m_customTabWidget->addTab(m_mainStackedWidget, deviceIcon, "iDevice");
m_customTabWidget->addTab(new AppsWidget(this), "Apps");
m_customTabWidget->addTab(new ToolboxWidget(this), "Toolbox");
auto *jailbrokenWidget = new JailbrokenWidget(this);
m_customTabWidget->addTab(jailbrokenWidget, "Jailbroken");
m_customTabWidget->finalizeStyles();
// todo: is this ok ?
auto connection = std::make_shared<QMetaObject::Connection>();
*connection =
connect(m_customTabWidget, &CustomTabWidget::currentChanged, this,
[this, jailbrokenWidget, connection](int index) {
if (index == 3) { // Jailbroken tab
jailbrokenWidget->initWidget();
QObject::disconnect(*connection);
}
});
// settings button
QPushButton *settingsButton = new QPushButton();
settingsButton->setIcon(QIcon::fromTheme("preferences-system"));
settingsButton->setIcon(QIcon(":/icons/MingcuteSettings7Line.png"));
settingsButton->setToolTip("Settings");
settingsButton->setFlat(true);
settingsButton->setCursor(Qt::PointingHandCursor);
@@ -141,23 +200,23 @@ MainWindow::MainWindow(QWidget *parent)
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));
auto *jailbrokenWidget = new JailbrokenWidget(this);
ui->mainTabWidget->widget(3)->layout()->addWidget(jailbrokenWidget);
m_connectedDeviceCountLabel = new QLabel("iDescriptor: no devices");
m_connectedDeviceCountLabel->setContentsMargins(5, 0, 5, 0);
m_connectedDeviceCountLabel->setStyleSheet(
"QLabel:hover { background-color : #13131319; }");
// TODO: is this a good idea?
auto connection = std::make_shared<QMetaObject::Connection>();
*connection = connect(ui->mainTabWidget, &QTabWidget::currentChanged, this,
[this, jailbrokenWidget, connection](int index) {
if (index == 3) { // Jailbroken tab
jailbrokenWidget->initWidget();
QObject::disconnect(*connection);
}
});
ui->statusbar->addWidget(m_connectedDeviceCountLabel);
ui->statusbar->setContentsMargins(0, 0, 0, 0);
// QWidget *statusSpacer = new QWidget();
// statusSpacer->setSizePolicy(QSizePolicy::Expanding,
// QSizePolicy::Preferred);
// statusSpacer->setAttribute(Qt::WA_TransparentForMouseEvents);
// ui->statusbar->addWidget(statusSpacer);
ui->statusbar->addPermanentWidget(settingsButton);
irecv_error_t res_recovery =
irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr);
@@ -169,16 +228,38 @@ MainWindow::MainWindow(QWidget *parent)
if (res != IDEVICE_E_SUCCESS) {
printf("ERROR: Unable to subscribe to device events.\n");
}
createMenus();
}
void MainWindow::createMenus()
{
QMenu *actionsMenu = menuBar()->addMenu("&Actions");
// Add a custom "About" action for your app
QAction *aboutAct = new QAction("&About iDescriptor", this);
connect(aboutAct, &QAction::triggered, this, [=]() {
QMessageBox::about(this, "About iDescriptor",
"<b>iDescriptor</b><br>"
"A modern device management tool.");
});
actionsMenu->addAction(aboutAct);
}
void MainWindow::updateNoDevicesConnected()
{
qDebug() << "Is there no devices connected? "
<< AppContext::sharedInstance()->noDevicesConnected();
if (AppContext::sharedInstance()->noDevicesConnected())
return ui->stackedWidget->setCurrentIndex(
0); // Show "No Devices Connected" page
ui->stackedWidget->setCurrentIndex(1); // Show device list page
if (AppContext::sharedInstance()->noDevicesConnected()) {
m_connectedDeviceCountLabel->setText("iDescriptor: no devices");
return m_mainStackedWidget->setCurrentIndex(
0); // Show "No Devices Connected" page
}
int deviceCount = AppContext::sharedInstance()->getConnectedDeviceCount();
m_connectedDeviceCountLabel->setText(
"iDescriptor: " + QString::number(deviceCount) +
(deviceCount == 1 ? " device" : " devices") + " connected");
m_mainStackedWidget->setCurrentIndex(1); // Show device list page
}
void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj)
@@ -187,7 +268,7 @@ void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj)
// TODO: handle
return;
try {
ui->stackedWidget->setCurrentIndex(1);
m_mainStackedWidget->setCurrentIndex(1);
RecoveryDeviceInfo *device =
qobject_cast<RecoveryDeviceInfo *>(recoveryDeviceInfoObj);
if (!device) {
+11 -11
View File
@@ -1,11 +1,12 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "customtabwidget.h"
#include "devicemanagerwidget.h"
#include "devicemenuwidget.h"
#include "iDescriptor.h"
#include "libirecovery.h"
#include <QLabel>
#include <QMainWindow>
#include <libimobiledevice/libimobiledevice.h>
QT_BEGIN_NAMESPACE
namespace Ui
@@ -21,21 +22,20 @@ class MainWindow : public QMainWindow
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void deviceAdded(QString udid); // Signal for device connections
void onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj);
void onRecoveryDeviceRemoved(QObject *deviceInfoObj);
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);
private:
void updateNoDevicesConnected();
private:
void createMenus();
Ui::MainWindow *ui;
DeviceManagerWidget *m_deviceManager; // Add this member
CustomTabWidget *m_customTabWidget;
DeviceManagerWidget *m_deviceManager;
QStackedWidget *m_mainStackedWidget;
QLabel *m_connectedDeviceCountLabel;
};
#endif // MAINWINDOW_H
+2 -136
View File
@@ -10,142 +10,6 @@
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="mainTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="mainTab">
<attribute name="title">
<string>iDevice</string>
</attribute>
<layout class="QVBoxLayout" name="mainTabLayout">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="noDeviceLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>No devices detected</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="devicePageLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="appsTab">
<attribute name="title">
<string>Apps</string>
</attribute>
<layout class="QVBoxLayout" name="appsTabLayout"/>
</widget>
<widget class="QWidget" name="toolboxTab">
<attribute name="title">
<string>Toolbox</string>
</attribute>
<layout class="QVBoxLayout" name="toolboxTabLayout"/>
</widget>
<widget class="QWidget" name="jailbrokenTab">
<attribute name="title">
<string>Jailbroken</string>
</attribute>
<layout class="QVBoxLayout" name="jailbrokenTabLayout">
<item>
<widget class="QLabel" name="jailbrokenLabel">
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
@@ -157,6 +21,8 @@
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<layout class="QHBoxLayout" name="horizontalLayout">
</layout>
</widget>
<resources/>
<connections/>
+42
View File
@@ -0,0 +1,42 @@
#include <Cocoa/Cocoa.h>
#include <QDebug>
#include <QMainWindow>
void setupMacOSWindow(QMainWindow *window)
{
if (!window) {
qWarning() << "setupMacOSWindow: window is null";
return;
}
NSView *nativeView = reinterpret_cast<NSView *>(window->winId());
NSWindow *nativeWindow = [nativeView window];
if (!nativeWindow) {
qWarning() << "setupMacOSWindow: native window is null";
return;
}
qDebug() << "Setting up macOS window styles";
window->setUnifiedTitleAndToolBarOnMac(true);
[nativeWindow setStyleMask:[nativeWindow styleMask] |
NSWindowStyleMaskFullSizeContentView |
NSWindowTitleHidden];
[nativeWindow setTitleVisibility:NSWindowTitleHidden];
[nativeWindow setTitlebarAppearsTransparent:YES];
NSToolbar *toolbar =
[[NSToolbar alloc] initWithIdentifier:@"HiddenInsetToolbar"];
toolbar.showsBaselineSeparator =
NO; // equivalent to HideToolbarSeparator: true
[nativeWindow setToolbar:toolbar];
// [toolbar setVisible:NO];
// todo : is it ok ?
[toolbar release];
// [nativeWindow setContentBorderThickness:0.0 forEdge:NSMinYEdge];
[nativeWindow center];
}
+10 -6
View File
@@ -71,6 +71,7 @@ ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent}
void ToolboxWidget::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(5, 5, 5, 5);
// Device selection section
QHBoxLayout *deviceLayout = new QHBoxLayout();
@@ -80,6 +81,7 @@ void ToolboxWidget::setupUI()
deviceLayout->addWidget(m_deviceLabel);
deviceLayout->addWidget(m_deviceCombo);
deviceLayout->setContentsMargins(0, 0, 0, 0);
deviceLayout->addStretch();
mainLayout->addLayout(deviceLayout);
@@ -88,6 +90,9 @@ void ToolboxWidget::setupUI()
m_scrollArea = new QScrollArea();
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_scrollArea->setStyleSheet(
"QScrollArea { background: transparent; border: none; }");
m_scrollArea->viewport()->setStyleSheet("background: transparent;");
m_contentWidget = new QWidget();
m_gridLayout = new QGridLayout(m_contentWidget);
@@ -164,7 +169,7 @@ QWidget *ToolboxWidget::createToolbox(const QString &title,
QFrame *frame = new QFrame();
frame->setObjectName("toolboxFrame");
frame->setFrameStyle(QFrame::Box);
frame->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; "
frame->setStyleSheet("#toolboxFrame { "
"border-radius: 5px; padding: 5px; }");
frame->setFixedSize(200, 120);
@@ -261,13 +266,12 @@ void ToolboxWidget::updateToolboxStates()
toolbox->setEnabled(enabled);
if (enabled) {
toolbox->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; "
toolbox->setStyleSheet("#toolboxFrame { "
"border-radius: 5px; padding: 5px; }");
} else {
toolbox->setStyleSheet(
"#toolboxFrame { border: 1px solid #ccc; border-radius: 5px; "
"padding: "
"5px; background-color: #f0f0f0; color: #999; }");
toolbox->setStyleSheet("#toolboxFrame { border-radius: 5px; "
"padding: 5px;"
"opacity: 0.45; }");
}
}
}