diff --git a/src/bin/nsapps/RV/main.cpp b/src/bin/nsapps/RV/main.cpp index 083b462fc..7824bb510 100644 --- a/src/bin/nsapps/RV/main.cpp +++ b/src/bin/nsapps/RV/main.cpp @@ -297,6 +297,13 @@ int main(int argc, char* argv[]) // Device. QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + // On macOS, ensure that the widget-based implementation is used instead of the native dialog. + // Rationale: + // - This prevents this Tahoe specific Qt bug (dialog buttons not clickable on macOS 26 Tahoe) : + // https://github.com/PCSX2/pcsx2/issues/13216 + // - This will make RV more future proof when newer macOS versions are released + QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); + // Now supporting high DPI displays by default // Setting the following environment variable, disable the high DPI support const bool noHighDPISupport = getenv("RV_NO_QT_HDPI_SUPPORT") != nullptr; diff --git a/src/lib/app/RvCommon/CMakeLists.txt b/src/lib/app/RvCommon/CMakeLists.txt index 5f22369d6..d8b32daef 100644 --- a/src/lib/app/RvCommon/CMakeLists.txt +++ b/src/lib/app/RvCommon/CMakeLists.txt @@ -60,7 +60,6 @@ SET(_sources RvProfileManager.cpp StreamConnection.cpp RvJavaScriptObject.cpp - QAlertPanel.cpp ) # IF(RV_TARGET_DARWIN) LIST(APPEND _sources CGDesktopVideoDevice.cpp CGDesktopVideoDeviceArm.cpp DisplayLink.cpp) ENDIF() diff --git a/src/lib/app/RvCommon/MuUICommands.cpp b/src/lib/app/RvCommon/MuUICommands.cpp index e63fa1f52..5927945b5 100644 --- a/src/lib/app/RvCommon/MuUICommands.cpp +++ b/src/lib/app/RvCommon/MuUICommands.cpp @@ -41,7 +41,6 @@ #include #include #include -#include "QAlertPanel.h" #if defined(RV_VFX_CY2023) #include @@ -962,7 +961,7 @@ namespace Rv const StringType::String* b2 = NODE_ARG_OBJECT(5, StringType::String); const StringType::String* b3 = NODE_ARG_OBJECT(6, StringType::String); - QAlertPanel box(doc); + QMessageBox box(doc); QString temp = UTF8::qconvert(title->c_str()); if (msg && *msg != *title) @@ -982,25 +981,25 @@ namespace Rv box.setWindowModality(Qt::WindowModal); #endif - QPushButton* q1 = box.addButton(UTF8::qconvert(b1->c_str()), QAlertPanel::AcceptRole); - QPushButton* q2 = b2 ? box.addButton(UTF8::qconvert(b2->c_str()), QAlertPanel::RejectRole) : 0; - QPushButton* q3 = b3 ? box.addButton(UTF8::qconvert(b3->c_str()), QAlertPanel::ApplyRole) : 0; + QPushButton* q1 = box.addButton(UTF8::qconvert(b1->c_str()), QMessageBox::AcceptRole); + QPushButton* q2 = b2 ? box.addButton(UTF8::qconvert(b2->c_str()), QMessageBox::RejectRole) : 0; + QPushButton* q3 = b3 ? box.addButton(UTF8::qconvert(b3->c_str()), QMessageBox::ApplyRole) : 0; switch (type) { case 0: // Info - box.setIcon(QAlertPanel::Information); + box.setIcon(QMessageBox::Information); break; case 1: // Warning - box.setIcon(QAlertPanel::Warning); + box.setIcon(QMessageBox::Warning); break; case 2: // Error - box.setIcon(QAlertPanel::Critical); + box.setIcon(QMessageBox::Critical); break; } diff --git a/src/lib/app/RvCommon/QAlertPanel.cpp b/src/lib/app/RvCommon/QAlertPanel.cpp deleted file mode 100644 index 481dfced4..000000000 --- a/src/lib/app/RvCommon/QAlertPanel.cpp +++ /dev/null @@ -1,302 +0,0 @@ -//****************************************************************************** -// Copyright (c) 2025 Autodesk. -// All rights reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// -//****************************************************************************** -#include "QAlertPanel.h" -#include -#include -#include -#include -#include -#include -#include -#include - -QAlertPanel::QAlertPanel(QWidget* parent) - : QDialog(parent) - , m_iconLabel(nullptr) - , m_textLabel(nullptr) - , m_clickedButton(nullptr) - , m_mainLayout(nullptr) - , m_buttonLayout(nullptr) - , m_contentLayout(nullptr) - , m_iconType(NoIcon) - , m_rejectButton(nullptr) -{ - setupUI(); -} - -QAlertPanel::~QAlertPanel() {} - -void QAlertPanel::setupUI() -{ - setModal(true); - setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - - m_mainLayout = new QVBoxLayout(this); - m_mainLayout->setSpacing(20); - m_mainLayout->setContentsMargins(20, 20, 20, 20); - - // Content layout (icon + text) - m_contentLayout = new QHBoxLayout(); - m_contentLayout->setSpacing(15); - - // Icon label - m_iconLabel = new QLabel(); - m_iconLabel->setFixedSize(32, 32); - m_iconLabel->setScaledContents(true); - m_iconLabel->setAlignment(Qt::AlignCenter); - m_contentLayout->addWidget(m_iconLabel); - - // Text label - m_textLabel = new QLabel(); - m_textLabel->setWordWrap(true); - m_textLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); - m_textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - m_textLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_textLabel->setFixedSize(100, 50); // by default, 2x large as high - m_textLabel->setFocusPolicy(Qt::NoFocus); // Prevent text from taking focus - m_contentLayout->addWidget(m_textLabel, 1); - - m_mainLayout->addLayout(m_contentLayout); - - // Button layout - m_buttonLayout = new QHBoxLayout(); - m_buttonLayout->setSpacing(10); - m_buttonLayout->addStretch(); - m_mainLayout->addLayout(m_buttonLayout); - - // Set default icon - updateIcon(); - - // Set minimum size and enable auto-sizing - setMinimumSize(300, 120); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); -} - -void QAlertPanel::setText(const QString& text) -{ - m_text = text; - m_textLabel->setText(text); - - // Don't resize here - let exec() handle the sizing -} - -void QAlertPanel::setIcon(IconType icon) -{ - m_iconType = icon; - updateIcon(); -} - -QPushButton* QAlertPanel::addButton(const QString& text, ButtonRole role) -{ - QPushButton* button = new QPushButton(text); - button->setFocusPolicy(Qt::StrongFocus); - - button->setDefault(false); - button->setAutoDefault(false); - - m_buttonLayout->addWidget(button); - - // Store reject button for ESCAPE key handling - if (role == RejectRole) - { - m_rejectButton = button; - } - - // Connect button click to our handler - connect(button, &QPushButton::clicked, this, &QAlertPanel::onButtonClicked); - - // Store reference for clickedButton() - connect(button, &QPushButton::clicked, [this, button]() { m_clickedButton = button; }); - - return button; -} - -QPushButton* QAlertPanel::clickedButton() const { return m_clickedButton; } - -QPushButton* QAlertPanel::defaultButton() const -{ - QList buttons = findChildren(); - for (QPushButton* button : buttons) - { - if (button->isDefault()) - { - return button; - } - } - return nullptr; -} - -QPushButton* QAlertPanel::rejectButton() const { return m_rejectButton; } - -QPushButton* QAlertPanel::nextPrevButton(QPushButton* fromButton, bool next) const -{ - QList buttons = findChildren(); - if (buttons.isEmpty()) - return nullptr; - - int index = buttons.indexOf(fromButton); - if (index == -1) - return nullptr; - - if (buttons.size() == 1) - return fromButton; - - int newIndex = next ? (index + 1) % buttons.size() : (index - 1 + buttons.size()) % buttons.size(); - - return buttons.at(newIndex); -} - -int QAlertPanel::exec() -{ - // Adjust dialog size for optimal text wrapping - adjustBestFitDimensions(); - - // Ensure the dialog is properly centered and visible - if (parentWidget()) - { - move(parentWidget()->geometry().center() - rect().center()); - } - - // Set first button as default for visual indication (our "focus" indicator) - QList buttons = findChildren(); - if (!buttons.isEmpty()) - { - buttons.first()->setFocus(); - buttons.first()->setDefault(true); - } - - return QDialog::exec(); -} - -void QAlertPanel::adjustBestFitDimensions() -{ - // Calculate optimal size based on text content for nice text wrapping - if (!m_text.isEmpty()) - { - // Get the text size with word wrapping at a reasonable width - QFontMetrics fm(m_textLabel->font()); - - // Try different widths to find the best text wrapping - float bestFitRatio = 1e100; - // Start with a reasonable width to acount at least for margins and - // such: left margin (20) + icon (32) + spacing (15) + right margin (20) - // = 87 - int bestWidth = 100; - int bestHeight = 50; - - float targetBestFitRatio = 2.0; - - // Test widths from 100 to 1000 pixels to find optimal wrapping - for (int testWidth = 100; testWidth <= 1000; testWidth += 20) - { - QRect textRect = fm.boundingRect(QRect(0, 0, testWidth, 0), Qt::TextWordWrap, m_text); - - float fitRatio = testWidth / (float)textRect.height(); - - // Only consider ratios that are >= targetBestFitRatio - if (fitRatio >= targetBestFitRatio) - { - float ratioDifference = fabs(fitRatio - targetBestFitRatio); - - if (ratioDifference < fabs(bestFitRatio - targetBestFitRatio)) - { - bestFitRatio = fitRatio; - bestWidth = testWidth; - bestHeight = textRect.height(); - } - } - } - - // set the size of the text label as per calculated dimensions. - m_textLabel->setFixedSize(bestWidth, bestHeight); - } - else - { - // Default size if no text - resize(300, 120); - } -} - -void QAlertPanel::onButtonClicked() { accept(); } - -void QAlertPanel::updateIcon() -{ - if (!m_iconLabel) - return; - - QStyle* style = QApplication::style(); - QIcon icon; - - switch (m_iconType) - { - case Information: - icon = style->standardIcon(QStyle::SP_MessageBoxInformation); - break; - case Warning: - icon = style->standardIcon(QStyle::SP_MessageBoxWarning); - break; - case Critical: - icon = style->standardIcon(QStyle::SP_MessageBoxCritical); - break; - case NoIcon: - default: - m_iconLabel->setVisible(false); - return; - } - - m_iconLabel->setPixmap(icon.pixmap(32, 32)); - m_iconLabel->setVisible(true); -} - -bool QAlertPanel::event(QEvent* event) -{ - if (event->type() == QEvent::KeyPress) - { - QKeyEvent* keyEvent = static_cast(event); - - if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) - { - event->accept(); - - if (defaultButton()) - defaultButton()->click(); - - return true; - } - - if (keyEvent->key() == Qt::Key_Escape) - { - event->accept(); - - if (rejectButton()) - rejectButton()->click(); - - return true; - } - } - - // Let Qt handle other events - return QDialog::event(event); -} - -bool QAlertPanel::focusNextPrevChild(bool next) -{ - QPushButton* defButton = defaultButton(); - if (!defButton) - return false; - - QPushButton* npButton = nextPrevButton(defButton, next); - if (npButton) - { - npButton->setFocus(); - npButton->setDefault(true); - } - return true; -} - -#include "QAlertPanel.moc" diff --git a/src/lib/app/RvCommon/QAlertPanel.h b/src/lib/app/RvCommon/QAlertPanel.h deleted file mode 100644 index 956fe2453..000000000 --- a/src/lib/app/RvCommon/QAlertPanel.h +++ /dev/null @@ -1,78 +0,0 @@ -//****************************************************************************** -// Copyright (c) 2024 Autodesk. -// All rights reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// -//****************************************************************************** -#ifndef __RvCommon__QAlertPanel__h__ -#define __RvCommon__QAlertPanel__h__ - -#include -#include -#include -#include -#include -#include - -class QAlertPanel : public QDialog -{ - Q_OBJECT - -public: - enum IconType - { - NoIcon = 0, - Information = 1, - Warning = 2, - Critical = 3 - }; - - enum ButtonRole - { - AcceptRole = 0, - RejectRole = 1, - ApplyRole = 2 - }; - - explicit QAlertPanel(QWidget* parent = nullptr); - virtual ~QAlertPanel(); - - // QMessageBox-compatible interface - void setText(const QString& text); - void setIcon(IconType icon); - - QPushButton* addButton(const QString& text, ButtonRole role); - QPushButton* clickedButton() const; - QPushButton* defaultButton() const; - QPushButton* rejectButton() const; - QPushButton* nextPrevButton(QPushButton* fromButton, bool next) const; - - int exec() override; - -protected: - bool event(QEvent* event) override; - bool focusNextPrevChild(bool next) override; - -private slots: - void onButtonClicked(); - -private: - void setupUI(); - void updateIcon(); - void adjustBestFitDimensions(); - - QLabel* m_iconLabel; - QLabel* m_textLabel; - QPushButton* m_clickedButton; - QVBoxLayout* m_mainLayout; - QHBoxLayout* m_buttonLayout; - QHBoxLayout* m_contentLayout; - - IconType m_iconType; - QString m_text; - - QPushButton* m_rejectButton; -}; - -#endif // __RvCommon__QAlertPanel__h__