From f0fede4e8199b14a9cdae6bcbc68903ffb41d9c8 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Thu, 2 Oct 2025 09:29:55 -0700 Subject: [PATCH] Refactor and enhance UI components - Updated `CableInfoWidget` to include a TODO comment regarding manufacturer verification. - Refactored `CustomTab` and `CustomTabWidget` to remove notification label functionality, simplifying the class structure. - Improved `DeviceInfoWidget` by adding a destructor to manage graphics view memory and initializing graphics scene properly. - Introduced `DiskUsageBar` and `DiskUsageWidget` classes to manage disk usage visualization, including hover popover functionality for detailed information. - Enhanced `MediaPreviewDialog` to include more descriptive window titles and adjusted status label styling based on platform. - Added platform-specific functionality in `macos.h` and `macos.mm` for popover management. - Cleaned up `ToolboxWidget` by adjusting label styles and removing fixed sizes for better responsiveness. --- src/appswidget.cpp | 82 ++++------ src/cableinfowidget.cpp | 3 +- src/customtabwidget.cpp | 118 +------------- src/customtabwidget.h | 10 -- src/deviceinfowidget.cpp | 29 +++- src/deviceinfowidget.h | 7 +- src/diskusagebar.cpp | 64 ++++++++ src/diskusagebar.h | 32 ++++ src/diskusagewidget.cpp | 325 ++++++++++++++++++++++++++++--------- src/diskusagewidget.h | 34 ++++ src/gallerywidget.cpp | 1 + src/iDescriptor-ui.h | 8 +- src/ifusemanager.h | 8 +- src/mediapreviewdialog.cpp | 10 +- src/platform/macos.h | 17 ++ src/platform/macos.mm | 123 +++++++++++++- src/toolboxwidget.cpp | 5 +- 17 files changed, 605 insertions(+), 271 deletions(-) create mode 100644 src/diskusagebar.cpp create mode 100644 src/diskusagebar.h create mode 100644 src/platform/macos.h diff --git a/src/appswidget.cpp b/src/appswidget.cpp index df7f8a5..ee2cd0c 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -58,19 +58,16 @@ void AppsWidget::setupUI() mainLayout->setSpacing(0); // Header with login - QFrame *headerFrame = new QFrame(); - headerFrame->setFixedHeight(60); - headerFrame->setStyleSheet("border-bottom: 1px solid #dee2e6;"); + QWidget *headerWidget = new QWidget(); + headerWidget->setFixedHeight(60); + headerWidget->setStyleSheet("border-bottom: 1px solid #dee2e6;"); - QHBoxLayout *headerLayout = new QHBoxLayout(headerFrame); + QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget); headerLayout->setContentsMargins(20, 10, 20, 10); QLabel *titleLabel = new QLabel("App Store"); titleLabel->setStyleSheet( "font-size: 24px; font-weight: bold; color: #333;"); - headerLayout->addWidget(titleLabel); - - headerLayout->addStretch(); // Create status label first m_statusLabel = new QLabel("Not signed in"); @@ -120,22 +117,17 @@ void AppsWidget::setupUI() } } m_statusLabel->setStyleSheet("font-size: 14px; color: #666;"); - headerLayout->addWidget(m_statusLabel); m_loginButton = new QPushButton(m_isLoggedIn ? "Sign Out" : "Sign In"); m_loginButton->setStyleSheet( "background-color: #007AFF; color: white; border: none; border-radius: " "4px; padding: 8px 16px; font-size: 14px;"); - headerLayout->addWidget(m_loginButton); - mainLayout->addWidget(headerFrame); - - // --- Search Bar --- - QHBoxLayout *searchContainerLayout = new QHBoxLayout(); - searchContainerLayout->setContentsMargins(20, 15, 20, 15); + mainLayout->addWidget(headerWidget); m_searchEdit = new QLineEdit(); - m_searchEdit->setPlaceholderText("Search for apps..."); + m_searchEdit->setPlaceholderText(m_isLoggedIn ? "Search for apps..." + : "Sign in to search"); m_searchEdit->setMaximumWidth(400); m_searchEdit->setStyleSheet("QLineEdit { " " padding: 8px; " @@ -151,19 +143,21 @@ void AppsWidget::setupUI() connect(searchAction, &QAction::triggered, this, &AppsWidget::performSearch); - searchContainerLayout->addStretch(); - searchContainerLayout->addWidget(m_searchEdit); - searchContainerLayout->addStretch(); + headerLayout->addWidget(titleLabel); - mainLayout->addLayout(searchContainerLayout); - - // --- Status and Login Button --- + headerLayout->addStretch(); + headerLayout->addWidget(m_searchEdit); + headerLayout->addStretch(); + headerLayout->addWidget(m_statusLabel); + headerLayout->addWidget(m_loginButton); // Scroll area for apps m_scrollArea = new QScrollArea(); m_scrollArea->setWidgetResizable(true); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setStyleSheet("border: none;"); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + m_scrollArea->viewport()->setStyleSheet("background: transparent;"); m_contentWidget = new QWidget(); QGridLayout *gridLayout = new QGridLayout(m_contentWidget); @@ -254,20 +248,11 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId, const QString &iconPath, QGridLayout *gridLayout, int row, int col) { - QFrame *cardFrame = new QFrame(); - cardFrame->setObjectName("cardFrame"); - cardFrame->setFixedSize(200, 250); - cardFrame->setStyleSheet("#cardFrame {" - " border: 1px solid #ddd;" - " border-radius: 8px;" - " background-color: #fff;" - "}" - "#cardFrame:hover {" - " border: 1.5px solid #007AFF;" - "}"); - cardFrame->setCursor(Qt::PointingHandCursor); + QWidget *cardWidget = new QWidget(); + // cardWidget->setFixedSize(200, 250); + cardWidget->setCursor(Qt::PointingHandCursor); - QVBoxLayout *cardLayout = new QVBoxLayout(cardFrame); + QHBoxLayout *cardLayout = new QHBoxLayout(cardWidget); cardLayout->setContentsMargins(15, 15, 15, 15); cardLayout->setSpacing(10); @@ -301,24 +286,29 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId, iconLabel->setPixmap(rounded); } }, - cardFrame); + cardWidget); + + // Vertical layout for name and description + QVBoxLayout *textLayout = new QVBoxLayout(); // App name QLabel *nameLabel = new QLabel(name); - nameLabel->setStyleSheet( - "font-size: 16px; font-weight: bold; color: #333;"); + nameLabel->setStyleSheet("font-size: 16px;"); nameLabel->setAlignment(Qt::AlignCenter); nameLabel->setWordWrap(true); - cardLayout->addWidget(nameLabel); + textLayout->addWidget(nameLabel); // App description QLabel *descLabel = new QLabel(description); descLabel->setStyleSheet("font-size: 12px; color: #666;"); descLabel->setAlignment(Qt::AlignCenter); descLabel->setWordWrap(true); - cardLayout->addWidget(descLabel); + textLayout->addWidget(descLabel); cardLayout->addStretch(); + cardLayout->addLayout(textLayout); + + QVBoxLayout *buttonsLayout = new QVBoxLayout(); // Install button placeholder QPushButton *installLabel = new QPushButton("Install"); @@ -336,15 +326,11 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId, connect(downloadIpaLabel, &QPushButton::clicked, this, [this, name, bundleId]() { onDownloadIpaClicked(name, bundleId); }); - cardLayout->addWidget(installLabel); - cardLayout->addWidget(downloadIpaLabel); + buttonsLayout->addWidget(installLabel); + buttonsLayout->addWidget(downloadIpaLabel); - // Make the entire card clickable - // cardFrame->mousePressEvent = [this, name, description](QMouseEvent *) { - // onAppCardClicked(name, description); - // }; - - gridLayout->addWidget(cardFrame, row, col); + cardLayout->addLayout(buttonsLayout); + gridLayout->addWidget(cardWidget, row, col); } void AppsWidget::onDownloadIpaClicked(const QString &name, const QString &bundleId) diff --git a/src/cableinfowidget.cpp b/src/cableinfowidget.cpp index f340af4..7050040 100644 --- a/src/cableinfowidget.cpp +++ b/src/cableinfowidget.cpp @@ -201,7 +201,8 @@ void CableInfoWidget::updateUI() QString statusText; QString statusStyle; QString iconText; - + // todo: sometimes they fake the manufacturer even if it's not genuine + // compare m_cableInfo.isTypeC to the actual values we get from ioreg if (m_cableInfo.isGenuine) { statusText = QString("Genuine %1") .arg(m_cableInfo.isTypeC ? "USB-C to Lightning Cable" diff --git a/src/customtabwidget.cpp b/src/customtabwidget.cpp index 5b56c13..b6bddad 100644 --- a/src/customtabwidget.cpp +++ b/src/customtabwidget.cpp @@ -8,35 +8,11 @@ // CustomTab implementation CustomTab::CustomTab(const QString &text, QWidget *parent) - : QPushButton(text, parent), m_notificationLabel(nullptr), - m_notificationCount(0) + : QPushButton(text, parent) { 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) @@ -45,58 +21,6 @@ void CustomTab::setIcon(const QIcon &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) @@ -115,7 +39,7 @@ CustomTabWidget::CustomTabWidget(QWidget *parent) // Style the tab bar m_tabBar->setStyleSheet("QWidget {" // " background-color: white;" - " border-radius: 35px;" + // " border-radius: 35px;" "}"); // Add drop shadow effect @@ -142,7 +66,7 @@ void CustomTabWidget::setupGlider() { m_glider = new QWidget(m_tabBar); m_glider->setStyleSheet("QWidget {" - " background-color: #185ee0;" + " background-color: #2b5693;" " border-radius: 1px;" "}"); // Set initial size - will be updated in animateGlider @@ -215,13 +139,6 @@ QWidget *CustomTabWidget::widget(int index) const 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(sender()); @@ -268,11 +185,11 @@ void CustomTabWidget::updateTabStyles() if (tab->isChecked()) { tab->setStyleSheet("CustomTab {" " color: #185ee0;" + // " color: #d7e1f4ff;" " font-weight: 500;" " font-size: 20px;" " border: none;" " outline: none;" - " border-radius: 27px;" " background-color: transparent;" "}" "CustomTab:hover {" @@ -281,11 +198,11 @@ void CustomTabWidget::updateTabStyles() } else { tab->setStyleSheet("CustomTab {" " color: #666;" + // " color: #2b5693;" " font-weight: 500;" " font-size: 20px;" " border: none;" " outline: none;" - " border-radius: 27px;" " background-color: transparent;" "}" "CustomTab:hover {" @@ -305,27 +222,4 @@ void CustomTabWidget::resizeEvent(QResizeEvent *event) // 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(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 \ No newline at end of file +} \ No newline at end of file diff --git a/src/customtabwidget.h b/src/customtabwidget.h index 327b1b6..4f7efd3 100644 --- a/src/customtabwidget.h +++ b/src/customtabwidget.h @@ -17,16 +17,7 @@ class CustomTab : public QPushButton 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 @@ -41,7 +32,6 @@ public: void setCurrentIndex(int index); int currentIndex() const; QWidget *widget(int index) const; - void setTabNotification(int index, int count); signals: void currentChanged(int index); diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index a04ba6e..a20496d 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -30,19 +31,18 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setContentsMargins(2, 2, 2, 2); mainLayout->setSpacing(2); - - QGraphicsScene *scene = new QGraphicsScene(this); + m_graphicsScene = new QGraphicsScene(this); // no parent QGraphicsPixmapItem *pixmapItem = new QGraphicsPixmapItem(QPixmap(":/resources/iphone.png")); - scene->addItem(pixmapItem); + m_graphicsScene->addItem(pixmapItem); - QGraphicsView *graphicsView = new ResponsiveGraphicsView(scene, this); - graphicsView->setRenderHint(QPainter::Antialiasing); - graphicsView->setMinimumWidth(200); - graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); - graphicsView->setStyleSheet("background: transparent; border: none;"); + m_graphicsView = new ResponsiveGraphicsView(m_graphicsScene, this); + m_graphicsView->setRenderHint(QPainter::Antialiasing); + m_graphicsView->setMinimumWidth(200); + m_graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); + m_graphicsView->setStyleSheet("background: transparent; border: none;"); - mainLayout->addWidget(graphicsView, 1); // Stretch factor 1 + mainLayout->addWidget(m_graphicsView, 1); // Stretch factor 1 // Right side: Info Table QWidget *infoContainer = new QWidget(); @@ -161,6 +161,8 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) " background-color: " + background.name() + ";" + // " background-color: #161d37;" + // " border: 1px solid #29356b;" " border-radius: 8px;" "}"); @@ -317,6 +319,15 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) m_updateTimer->start(30000); // Update every 30 seconds } +DeviceInfoWidget::~DeviceInfoWidget() +{ + if (m_graphicsView) { + m_graphicsView->setScene( + nullptr); // prevents QGraphicsScene from calling into view during + // its destructor only needed on macos ? + } +} + void DeviceInfoWidget::onBatteryMoreClicked() { QMessageBox msgBox; diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 22281bc..950a27e 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -2,16 +2,18 @@ #define DEVICEINFOWIDGET_H #include "batterywidget.h" #include "iDescriptor.h" +#include +#include #include #include #include - class DeviceInfoWidget : public QWidget { Q_OBJECT public: explicit DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent = nullptr); + ~DeviceInfoWidget(); // added destructor private slots: void onBatteryMoreClicked(); @@ -26,6 +28,9 @@ private: QLabel *m_chargingWattsWithCableTypeLabel; BatteryWidget *m_batteryWidget; QLabel *m_lightningIconLabel; + + QGraphicsView *m_graphicsView = nullptr; + QGraphicsScene *m_graphicsScene = nullptr; }; #endif // DEVICEINFOWIDGET_H diff --git a/src/diskusagebar.cpp b/src/diskusagebar.cpp new file mode 100644 index 0000000..3839dee --- /dev/null +++ b/src/diskusagebar.cpp @@ -0,0 +1,64 @@ +#include "diskusagebar.h" +#include "platform/macos.h" + +#include +#include +#include + +DiskUsageBar::DiskUsageBar(QWidget *parent) : QWidget(parent), m_percentage(0.0) +{ + m_hoverTimer = new QTimer(this); + m_hoverTimer->setSingleShot(true); + m_hoverTimer->setInterval(500); // 500ms delay before showing popover + connect(m_hoverTimer, &QTimer::timeout, this, &DiskUsageBar::showPopover); + setAttribute(Qt::WA_Hover, true); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + // Add an invisible spacer to give the widget content + QWidget *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(spacer); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +void DiskUsageBar::setUsageInfo(const QString &type, + const QString &formattedSize, + const QString &color, double percentage) +{ + m_type = type; + m_formattedSize = formattedSize; + m_color = color; + m_percentage = percentage; +} + +void DiskUsageBar::enterEvent(QEnterEvent *event) +{ + Q_UNUSED(event); + m_hoverTimer->start(); + QWidget::enterEvent(event); +} + +void DiskUsageBar::leaveEvent(QEvent *event) +{ + m_hoverTimer->stop(); + hidePopoverForBarWidget(); + QWidget::leaveEvent(event); +} + +void DiskUsageBar::showPopover() +{ + if (m_type.isEmpty()) + return; + + UsageInfo info; + info.type = m_type; + info.formattedSize = m_formattedSize; + info.color = m_color; + info.percentage = m_percentage; + + showPopoverForBarWidget(this, info); +} \ No newline at end of file diff --git a/src/diskusagebar.h b/src/diskusagebar.h new file mode 100644 index 0000000..9f903d2 --- /dev/null +++ b/src/diskusagebar.h @@ -0,0 +1,32 @@ +#ifndef DISKUSAGEBAR_H +#define DISKUSAGEBAR_H + +#include +#include + +class DiskUsageBar : public QWidget +{ + Q_OBJECT + +public: + explicit DiskUsageBar(QWidget *parent = nullptr); + + void setUsageInfo(const QString &type, const QString &formattedSize, + const QString &color, double percentage); + +protected: + void enterEvent(QEnterEvent *event) override; + void leaveEvent(QEvent *event) override; + +private slots: + void showPopover(); + +private: + QString m_type; + QString m_formattedSize; + QString m_color; + double m_percentage; + QTimer *m_hoverTimer; +}; + +#endif // DISKUSAGEBAR_H \ No newline at end of file diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 08bce26..d405517 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -1,9 +1,10 @@ #include "diskusagewidget.h" +#include "diskusagebar.h" #include "iDescriptor.h" + #include #include #include -#include #include #include @@ -17,107 +18,283 @@ DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) m_freeSpace(0) { setMinimumHeight(80); + setupUI(); fetchData(); } +void DiskUsageWidget::setupUI() +{ + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + m_mainLayout->setSpacing(0); + + // Title + m_titleLabel = new QLabel("Disk Usage", this); + QFont titleFont = font(); + titleFont.setBold(true); + m_titleLabel->setFont(titleFont); + m_titleLabel->setAlignment(Qt::AlignCenter); + m_mainLayout->addWidget(m_titleLabel); + + // Status label (for loading/error states) + m_statusLabel = new QLabel(this); + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->setText("Loading disk usage..."); + m_mainLayout->addWidget(m_statusLabel); + + // Disk usage bar container + m_diskBarContainer = new QWidget(this); + m_diskBarContainer->setMinimumHeight(20); + m_diskBarContainer->setMaximumHeight(20); + m_diskBarContainer->setStyleSheet( + "QWidget#diskBarContainer { margin: 0; padding: 0; border: none; }"); + m_diskBarContainer->setObjectName("diskBarContainer"); + m_diskBarLayout = new QHBoxLayout(m_diskBarContainer); + m_diskBarLayout->setContentsMargins(0, 0, 0, 0); + m_diskBarLayout->setSpacing(0); + +// Create colored segments +#ifdef Q_OS_MAC + m_systemBar = new DiskUsageBar(); + m_appsBar = new DiskUsageBar(); + m_mediaBar = new DiskUsageBar(); + m_othersBar = new DiskUsageBar(); + m_freeBar = new DiskUsageBar(); +#else + m_systemBar = new QWidget(); + m_appsBar = new QWidget(); + m_mediaBar = new QWidget(); + m_othersBar = new QWidget(); + m_freeBar = new QWidget(); +#endif + // Set size policies to prevent any extra spacing + // m_systemBar->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Expanding); m_appsBar->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Expanding); + // m_mediaBar->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Expanding); + // m_othersBar->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Expanding); m_freeBar->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Expanding); + + // Set colors + m_systemBar->setStyleSheet( + "background-color: #a1384d; border: 1px solid" + "#e64a5b; padding: 0; margin: 0; border-top-left-radius: 3px; " + "border-bottom-left-radius: 3px;"); + m_appsBar->setStyleSheet("background-color: #4f869f; border: 1px solid " + "#63b4da; padding: 0; margin: 0;"); + m_mediaBar->setStyleSheet( + "background-color: #2ECC71; border: none; padding: 0; margin: 0;"); + m_othersBar->setStyleSheet("background-color: #a28729; border: 1px solid " + "#c4a32d; padding: 0; margin: 0;"); + m_freeBar->setStyleSheet( + "background-color: #474747; border: 1px solid " + "#4f4f4f; padding: 0; margin: 0; border-top-right-radius: 3px; " + "border-bottom-right-radius: 3px;"); + + m_diskBarLayout->addWidget(m_systemBar); + m_diskBarLayout->addWidget(m_appsBar); + m_diskBarLayout->addWidget(m_mediaBar); + m_diskBarLayout->addWidget(m_othersBar); + m_diskBarLayout->addWidget(m_freeBar); + + m_diskBarContainer->hide(); // Initially hidden + m_mainLayout->addWidget(m_diskBarContainer); + + // Legend layout + m_legendLayout = new QHBoxLayout(); + m_legendLayout->setSpacing(0); + m_legendLayout->setContentsMargins(0, 0, 0, 0); + + // Legend labels + m_systemLabel = new QLabel("System", this); + m_appsLabel = new QLabel("Apps", this); + m_mediaLabel = new QLabel("Media", this); + m_othersLabel = new QLabel("Others", this); + m_freeLabel = new QLabel("Free", this); + + // Style legend labels with colored backgrounds + QString labelStyle = "QLabel { padding: 2px 6px; margin: 0px; " + "border-radius: 3px; color: white; font-size: 10px; }"; + m_systemLabel->setStyleSheet(labelStyle + "background-color: #a1384d;"); + m_appsLabel->setStyleSheet(labelStyle + "background-color: #3498DB;"); + m_mediaLabel->setStyleSheet(labelStyle + "background-color: #2ECC71;"); + m_othersLabel->setStyleSheet(labelStyle + "background-color: #F39C12;"); + m_freeLabel->setStyleSheet(labelStyle + + "background-color: #BDC3C7; color: black;"); + + m_legendLayout->addWidget(m_systemLabel); + m_legendLayout->addWidget(m_appsLabel); + m_legendLayout->addWidget(m_mediaLabel); + m_legendLayout->addWidget(m_othersLabel); + m_legendLayout->addWidget(m_freeLabel); + m_legendLayout->addStretch(); + + // Hide legend initially + m_systemLabel->hide(); + m_appsLabel->hide(); + m_mediaLabel->hide(); + m_othersLabel->hide(); + m_freeLabel->hide(); + + m_mainLayout->addLayout(m_legendLayout); +} + QSize DiskUsageWidget::sizeHint() const { return QSize(400, 80); } -void DiskUsageWidget::paintEvent(QPaintEvent *event) +void DiskUsageWidget::updateUI() { - Q_UNUSED(event); - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - QColor textColor = qApp->palette().text().color(); - if (m_state == Loading) { - painter.setPen(textColor); - painter.drawText(rect(), Qt::AlignCenter, "Loading disk usage..."); + m_statusLabel->setText("Loading disk usage..."); + m_statusLabel->show(); + m_diskBarContainer->hide(); + m_systemLabel->hide(); + m_appsLabel->hide(); + m_mediaLabel->hide(); + m_othersLabel->hide(); + m_freeLabel->hide(); return; } if (m_state == Error) { - painter.setPen(textColor); - painter.drawText(rect(), Qt::AlignCenter, "Error: " + m_errorMessage); + m_statusLabel->setText("Error: " + m_errorMessage); + m_statusLabel->show(); + m_diskBarContainer->hide(); + m_systemLabel->hide(); + m_appsLabel->hide(); + m_mediaLabel->hide(); + m_othersLabel->hide(); + m_freeLabel->hide(); return; } - // Title - QFont titleFont = font(); - titleFont.setBold(true); - painter.setFont(titleFont); - painter.setPen(textColor); - QRectF titleRect(0, 5, width(), 20); - painter.drawText(titleRect, Qt::AlignHCenter | Qt::AlignTop, "Disk Usage"); - painter.setFont(font()); // Reset font - if (m_totalCapacity == 0) { - painter.setPen(Qt::black); - painter.drawText(QRect(0, 30, width(), height() - 30), Qt::AlignCenter, - "No disk information available."); + m_statusLabel->setText("No disk information available."); + m_statusLabel->show(); + m_diskBarContainer->hide(); + m_systemLabel->hide(); + m_appsLabel->hide(); + m_mediaLabel->hide(); + m_othersLabel->hide(); + m_freeLabel->hide(); return; } - painter.setPen(Qt::NoPen); + // Hide status label and show disk bar and legend + m_statusLabel->hide(); + m_diskBarContainer->show(); + m_systemLabel->show(); + m_appsLabel->show(); + m_mediaLabel->show(); + m_othersLabel->show(); + m_freeLabel->show(); - const int barHeight = 20; - QRectF barRect(10, 30, width() - 20, barHeight); + // Calculate proportions for each segment + int totalWidth = m_diskBarContainer->width(); - double scale = (double)barRect.width() / m_totalCapacity; - double currentX = barRect.left(); + int systemWidth = + (int)((double)m_systemUsage / m_totalCapacity * totalWidth); + int appsWidth = (int)((double)m_appsUsage / m_totalCapacity * totalWidth); + int mediaWidth = (int)((double)m_mediaUsage / m_totalCapacity * totalWidth); + int othersWidth = + (int)((double)m_othersUsage / m_totalCapacity * totalWidth); + int freeWidth = (int)((double)m_freeSpace / m_totalCapacity * totalWidth); - auto drawSegment = [&](uint64_t value, const QColor &color) { - if (value > 0) { - double segmentWidth = value * scale; - painter.fillRect( - QRectF(currentX, barRect.top(), segmentWidth, barRect.height()), - color); - currentX += segmentWidth; + // Ensure at least 1 pixel width for non-zero values + if (m_systemUsage > 0 && systemWidth == 0) + systemWidth = 1; + if (m_appsUsage > 0 && appsWidth == 0) + appsWidth = 1; + if (m_mediaUsage > 0 && mediaWidth == 0) + mediaWidth = 1; + if (m_othersUsage > 0 && othersWidth == 0) + othersWidth = 1; + if (m_freeSpace > 0 && freeWidth == 0) + freeWidth = 1; + + m_diskBarLayout->setStretchFactor(m_systemBar, systemWidth); + m_diskBarLayout->setStretchFactor(m_appsBar, appsWidth); + m_diskBarLayout->setStretchFactor(m_mediaBar, mediaWidth); + m_diskBarLayout->setStretchFactor(m_othersBar, othersWidth); + m_diskBarLayout->setStretchFactor(m_freeBar, freeWidth); + + // Hide segments with zero usage + // m_systemBar->setVisible(m_systemUsage > 0); + // m_appsBar->setVisible(m_appsUsage > 0); + // m_mediaBar->setVisible(m_mediaUsage > 0); + // m_othersBar->setVisible(m_othersUsage > 0); + // m_freeBar->setVisible(m_freeSpace > 0); + + // Format sizes for display + auto formatSize = [](uint64_t bytes) -> QString { + const char *units[] = {"B", "KB", "MB", "GB", "TB"}; + int unitIndex = 0; + double size = bytes; + + while (size >= 1024 && unitIndex < 4) { + size /= 1024; + unitIndex++; } + + return QString("%1 %2") + .arg(QString::number(size, 'f', 1)) + .arg(units[unitIndex]); }; - const QColor systemColor("#E74C3C"); - const QColor appsColor("#3498DB"); - const QColor mediaColor("#2ECC71"); - const QColor othersColor("#F39C12"); - const QColor freeColor("#BDC3C7"); + // Update legend labels with sizes + m_systemLabel->setText( + QString("System (%1)").arg(formatSize(m_systemUsage))); + m_appsLabel->setText(QString("Apps (%1)").arg(formatSize(m_appsUsage))); + m_mediaLabel->setText(QString("Media (%1)").arg(formatSize(m_mediaUsage))); + m_othersLabel->setText( + QString("Others (%1)").arg(formatSize(m_othersUsage))); + m_freeLabel->setText(QString("Free (%1)").arg(formatSize(m_freeSpace))); - // System - drawSegment(m_systemUsage, systemColor); - // Apps - drawSegment(m_appsUsage, appsColor); - // Media - drawSegment(m_mediaUsage, mediaColor); - // Others - drawSegment(m_othersUsage, othersColor); - // Free - drawSegment(m_freeSpace, freeColor); + qDebug() << "Disk Usage Updated:" + << "System:" << m_systemUsage << "Apps:" << m_appsUsage + << "Media:" << m_mediaUsage << "Others:" << m_othersUsage + << "Free:" << m_freeSpace; - // Legend - painter.setPen(textColor); - qreal legendY = barRect.bottom() + 15; - const int legendBoxSize = 10; - const int legendSpacing = 5; - qreal currentLegendX = barRect.left(); + // Set stretch factors and ensure minimum visibility + int systemStretch = std::max( + 1, (int)(m_systemUsage / 1000000)); // Convert to MB for stretch + int appsStretch = std::max(1, (int)(m_appsUsage / 1000000)); + int mediaStretch = std::max(1, (int)(m_mediaUsage / 1000000)); + int othersStretch = std::max(1, (int)(m_othersUsage / 1000000)); + int freeStretch = std::max(1, (int)(m_freeSpace / 1000000)); - auto drawLegendItem = [&](const QColor &color, const QString &text) { - painter.fillRect( - QRectF(currentLegendX, legendY, legendBoxSize, legendBoxSize), - color); - currentLegendX += legendBoxSize + legendSpacing; - painter.setPen(textColor); + m_diskBarLayout->setStretchFactor(m_systemBar, systemStretch); + m_diskBarLayout->setStretchFactor(m_appsBar, appsStretch); + m_diskBarLayout->setStretchFactor(m_mediaBar, mediaStretch); + m_diskBarLayout->setStretchFactor(m_othersBar, othersStretch); + m_diskBarLayout->setStretchFactor(m_freeBar, freeStretch); - QFontMetrics fm(font()); - QRect textRect = fm.boundingRect(text); - painter.drawText(QPointF(currentLegendX, legendY + legendBoxSize), - text); - currentLegendX += textRect.width() + legendSpacing * 2; - }; + // Set usage info for popovers +#ifdef Q_OS_MAC + m_systemBar->setUsageInfo("System", formatSize(m_systemUsage), "#a1384d", + (double)m_systemUsage / m_totalCapacity); + m_appsBar->setUsageInfo("Apps", formatSize(m_appsUsage), "#3498DB", + (double)m_appsUsage / m_totalCapacity); + m_mediaBar->setUsageInfo("Media", formatSize(m_mediaUsage), "#2ECC71", + (double)m_mediaUsage / m_totalCapacity); + m_othersBar->setUsageInfo("Others", formatSize(m_othersUsage), "#F39C12", + (double)m_othersUsage / m_totalCapacity); + m_freeBar->setUsageInfo("Free", formatSize(m_freeSpace), "#BDC3C7", + (double)m_freeSpace / m_totalCapacity); +#endif + // Hide segments with zero usage + // m_systemBar->setVisible(m_systemUsage > 0); + // m_appsBar->setVisible(m_appsUsage > 0); + // m_mediaBar->setVisible(m_mediaUsage > 0); + // m_othersBar->setVisible(m_othersUsage > 0); + // m_freeBar->setVisible(m_freeSpace > 0); +} - drawLegendItem(systemColor, "System"); - drawLegendItem(appsColor, "Apps"); - drawLegendItem(mediaColor, "Media"); - drawLegendItem(othersColor, "Others"); - drawLegendItem(freeColor, "Free"); +void DiskUsageWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + // No custom painting needed - using widgets and layouts } void DiskUsageWidget::fetchData() @@ -147,7 +324,7 @@ void DiskUsageWidget::fetchData() m_state = Ready; } - update(); // Trigger repaint + updateUI(); // Update the UI instead of triggering repaint watcher->deleteLater(); }); diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h index cf370ff..87fdfc9 100644 --- a/src/diskusagewidget.h +++ b/src/diskusagewidget.h @@ -1,7 +1,12 @@ #ifndef DISKUSAGEWIDGET_H #define DISKUSAGEWIDGET_H +#include "diskusagebar.h" #include "iDescriptor.h" +#include +#include +#include +#include #include #include @@ -18,6 +23,8 @@ protected: private: void fetchData(); + void setupUI(); + void updateUI(); enum State { Loading, Ready, Error }; @@ -25,6 +32,33 @@ private: State m_state; QString m_errorMessage; + // UI widgets + QVBoxLayout *m_mainLayout; + QLabel *m_titleLabel; + QLabel *m_statusLabel; + QWidget *m_diskBarContainer; + QHBoxLayout *m_diskBarLayout; +#ifdef Q_OS_MAC + DiskUsageBar *m_systemBar; + DiskUsageBar *m_appsBar; + DiskUsageBar *m_mediaBar; + DiskUsageBar *m_othersBar; + DiskUsageBar *m_freeBar; +#else + QWidget *m_systemBar; + QWidget *m_appsBar; + QWidget *m_mediaBar; + QWidget *m_othersBar; + QWidget *m_freeBar; +#endif + + QHBoxLayout *m_legendLayout; + QLabel *m_systemLabel; + QLabel *m_appsLabel; + QLabel *m_mediaLabel; + QLabel *m_othersLabel; + QLabel *m_freeLabel; + uint64_t m_totalCapacity; uint64_t m_systemUsage; uint64_t m_appsUsage; diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 78a716e..d3afa64 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -17,6 +17,7 @@ #include #include +// todo: create a service QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path) { uint64_t fd_handle = 0; diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 5ee2f8c..44f61c5 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -4,6 +4,10 @@ #include #include +#ifdef Q_OS_MAC +#include "./platform/macos.h" +#endif + #define COLOR_GREEN QColor(0, 180, 0) // Green #define COLOR_ORANGE QColor(255, 140, 0) // Orange #define COLOR_RED QColor(255, 0, 0) // Red @@ -50,10 +54,6 @@ protected: } }; -#ifdef Q_OS_MAC -void setupMacOSWindow(QMainWindow *window); -#endif - enum class iDescriptorTool { Airplayer, RealtimeScreen, diff --git a/src/ifusemanager.h b/src/ifusemanager.h index 5a59ed3..76b961a 100644 --- a/src/ifusemanager.h +++ b/src/ifusemanager.h @@ -7,12 +7,12 @@ class iFuseManager : public QObject { Q_OBJECT public: - // explicit iFuseManager(QObject *parent = nullptr); - static QList getMountPoints(); +// explicit iFuseManager(QObject *parent = nullptr); #ifdef Q_OS_LINUX - static QStringList getMountArg(std::string &udid, QString &path); + static QList getMountPoints(); #endif - // TODO: need to implement a cross-platform mount and unmount function + static QStringList getMountArg(std::string &udid, QString &path); + // TODO: need to implement a cross-platform mount and unmount method static bool linuxUnmount(const QString &path); signals: }; diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 1c0052b..dd3e406 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -39,7 +39,7 @@ MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device, m_fitToWindowBtn(nullptr), m_zoomFactor(1.0), m_isRepeatEnabled(true), m_isDraggingTimeline(false), m_videoDuration(0) { - setWindowTitle(QFileInfo(filePath).fileName()); + setWindowTitle(QFileInfo(filePath).fileName() + " - iDescriptor"); // Make dialog fullscreen setWindowState(Qt::WindowMaximized); @@ -88,9 +88,13 @@ void MediaPreviewDialog::setupUI() } // Status bar + // more margin because of border radius on macOS m_statusLabel = new QLabel(this); - m_statusLabel->setStyleSheet( - "QLabel { background: #f0f0f0; padding: 5px; font-size: 12px; }"); +#ifdef Q_OS_MAC + m_statusLabel->setStyleSheet("QLabel { margin-left: 15px; }"); +#else + m_statusLabel->setStyleSheet("QLabel { margin-left: 5px; }"); +#endif m_mainLayout->addWidget(m_statusLabel); } diff --git a/src/platform/macos.h b/src/platform/macos.h new file mode 100644 index 0000000..b621f7b --- /dev/null +++ b/src/platform/macos.h @@ -0,0 +1,17 @@ +#include +#include +#include +#include + +struct UsageInfo { + QString type; + QString formattedSize; + QString color; + double percentage; +}; + +void setupMacOSWindow(QMainWindow *window); + +void showPopoverForBarWidget(QWidget *widget, const UsageInfo &info); + +void hidePopoverForBarWidget(); \ No newline at end of file diff --git a/src/platform/macos.mm b/src/platform/macos.mm index 8ccbc77..bed7726 100644 --- a/src/platform/macos.mm +++ b/src/platform/macos.mm @@ -1,3 +1,4 @@ +#include "./macos.h" #include #include #include @@ -39,4 +40,124 @@ void setupMacOSWindow(QMainWindow *window) // [nativeWindow setContentBorderThickness:0.0 forEdge:NSMinYEdge]; [nativeWindow center]; -} \ No newline at end of file +} + +@interface DiskUsagePopoverViewController : NSViewController +@property(nonatomic, strong) NSTextField *typeLabel; +@property(nonatomic, strong) NSTextField *sizeLabel; +@property(nonatomic, strong) NSTextField *percentageLabel; +@end + +// Static variables for popover management +NSPopover *s_popover = nil; +NSViewController *s_viewController = nil; + +@implementation DiskUsagePopoverViewController +- (void)loadView +{ + NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 180, 80)]; + + // Type label + self.typeLabel = + [[NSTextField alloc] initWithFrame:NSMakeRect(40, 55, 130, 16)]; + self.typeLabel.editable = NO; + self.typeLabel.selectable = NO; + self.typeLabel.bordered = NO; + self.typeLabel.backgroundColor = [NSColor clearColor]; + self.typeLabel.font = [NSFont boldSystemFontOfSize:13]; + [view addSubview:self.typeLabel]; + + // Size label + self.sizeLabel = + [[NSTextField alloc] initWithFrame:NSMakeRect(10, 30, 160, 16)]; + self.sizeLabel.editable = NO; + self.sizeLabel.selectable = NO; + self.sizeLabel.bordered = NO; + self.sizeLabel.backgroundColor = [NSColor clearColor]; + self.sizeLabel.font = [NSFont systemFontOfSize:11]; + [view addSubview:self.sizeLabel]; + + // Percentage label + self.percentageLabel = + [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 160, 16)]; + self.percentageLabel.editable = NO; + self.percentageLabel.selectable = NO; + self.percentageLabel.bordered = NO; + self.percentageLabel.backgroundColor = [NSColor clearColor]; + self.percentageLabel.font = [NSFont systemFontOfSize:11]; + self.percentageLabel.textColor = [NSColor secondaryLabelColor]; + [view addSubview:self.percentageLabel]; + + self.view = view; +} + +- (void)updateWithInfo:(const UsageInfo &)info +{ + self.typeLabel.stringValue = + [NSString stringWithUTF8String:info.type.toUtf8().constData()]; + self.sizeLabel.stringValue = + [NSString stringWithUTF8String:info.formattedSize.toUtf8().constData()]; + self.percentageLabel.stringValue = [NSString + stringWithFormat:@"%.1f%% of total capacity", info.percentage]; +} + +@end + +void hidePopoverForBarWidget() +{ + if (s_popover) { + [s_popover close]; + [s_popover release]; + s_popover = nil; + } + if (s_viewController) { + [s_viewController release]; + s_viewController = nil; + } +} +// TODO: bug report to Qt, window becomes blurry, shifted or resized after +// showing popover +void showPopoverForBarWidget(QWidget *widget, const UsageInfo &info) +{ + if (!widget) + return; + + // Hide existing popover if any + hidePopoverForBarWidget(); + + // Get the native view + NSView *nativeView = reinterpret_cast(widget->winId()); + if (!nativeView) + return; + + NSWindow *window = [nativeView window]; + if (!window) + return; + + // Create view controller and force view loading + DiskUsagePopoverViewController *viewController = + [[DiskUsagePopoverViewController alloc] init]; + + // Force the view to load before updating + [viewController loadView]; + [viewController updateWithInfo:info]; + + // Create popover + NSPopover *popover = [[NSPopover alloc] init]; + [popover setContentSize:NSMakeSize(180, 80)]; + [popover setBehavior:NSPopoverBehaviorTransient]; + [popover setAnimates:YES]; + [popover setContentViewController:viewController]; + + // Use the widget's bounds for a simpler approach + NSRect widgetBounds = nativeView.bounds; + + // Show popover + [popover showRelativeToRect:widgetBounds + ofView:nativeView + preferredEdge:NSMinYEdge]; + + // Store references (retain them) + s_popover = [popover retain]; + s_viewController = [viewController retain]; +} diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 5a7a7fd..29a5694 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -167,7 +167,6 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, { ClickableWidget *b = new ClickableWidget(); b->setStyleSheet("padding: 5px; border: none; outline: none;"); - b->setFixedSize(200, 120); QVBoxLayout *layout = new QVBoxLayout(b); @@ -235,15 +234,13 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, } // Title QLabel *titleLabel = new QLabel(title); - titleLabel->setFont(QFont("Arial", 10, QFont::Bold)); titleLabel->setAlignment(Qt::AlignCenter); // Description QLabel *descLabel = new QLabel(description); - descLabel->setFont(QFont("Arial", 8)); descLabel->setWordWrap(true); descLabel->setAlignment(Qt::AlignCenter); - descLabel->setStyleSheet("color: #666;"); + descLabel->setStyleSheet("color: #666; font-size: 12px;"); layout->addWidget(iconLabel); layout->addWidget(titleLabel);