diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 72a569fc3..711d0ba0b 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -110,6 +110,7 @@ set(SOURCE_FILES ConfigController.cpp ColorPicker.cpp CoreManager.cpp + CoreConsumer.cpp CoreController.cpp Display.cpp DisplayGL.cpp @@ -152,6 +153,7 @@ set(SOURCE_FILES OverrideView.cpp PaletteView.cpp PlacementControl.cpp + PopupManager.cpp RegisterView.cpp ReportView.cpp ROMInfo.cpp diff --git a/src/platform/qt/CoreConsumer.cpp b/src/platform/qt/CoreConsumer.cpp new file mode 100644 index 000000000..6f5adac8f --- /dev/null +++ b/src/platform/qt/CoreConsumer.cpp @@ -0,0 +1,96 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CoreConsumer.h" + +#include "CoreController.h" + +using namespace QGBA; + +CoreProvider::CoreProvider(std::shared_ptr controller) { + setController(controller); +} + +CoreProvider::~CoreProvider() { + for (CoreConsumer* consumer : m_consumers) { + consumer->m_provider = nullptr; + } +} + +void CoreProvider::setController(std::shared_ptr controller) { + if (m_controller) { + // Disconnect all signals from old controller + QObject::disconnect(m_controller.get(), nullptr, this, nullptr); + } + std::shared_ptr oldController = m_controller; + m_controller = controller; + for (CoreConsumer* consumer : m_consumers) { + if (consumer->onControllerChanged) { + consumer->onControllerChanged(); + } + } +} + +void CoreProvider::setController(CoreController* controller) { + setController(std::shared_ptr(controller)); +} + +CoreController* CoreProvider::get() const { + return m_controller.get(); +} + +void CoreProvider::addConsumer(CoreConsumer* consumer) { + m_consumers.insert(consumer); +} + +void CoreProvider::removeConsumer(CoreConsumer* consumer) { + m_consumers.erase(consumer); +} + +void CoreProvider::swap(std::shared_ptr& controller) { + m_controller.swap(controller); +} + +CoreConsumer::CoreConsumer(CoreProvider* provider) { + setCoreProvider(provider); +} + +CoreConsumer::CoreConsumer(const CoreConsumer& other) { + setCoreProvider(other.m_provider); +} + +CoreConsumer::~CoreConsumer() { + if (m_provider) { + m_provider->removeConsumer(this); + } +} + +void CoreConsumer::setCoreProvider(CoreProvider* provider) { + if (m_provider) { + m_provider->removeConsumer(this); + } + m_provider = provider; + if (provider) { + provider->addConsumer(this); + } +} + +CoreController* CoreConsumer::controller() const { + if (!m_provider) { + return nullptr; + } + return m_provider->get(); +} + +std::shared_ptr CoreConsumer::sharedController() const { + if (!m_provider) { + return nullptr; + } + return *m_provider; +} + +void CoreConsumer::providerDestroyed() { + m_provider = nullptr; +} diff --git a/src/platform/qt/CoreConsumer.h b/src/platform/qt/CoreConsumer.h new file mode 100644 index 000000000..09d31daa4 --- /dev/null +++ b/src/platform/qt/CoreConsumer.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include + +#include +#include + +namespace QGBA { + +class CoreController; +class CoreConsumer; + +class CoreProvider : public QObject { +public: + CoreProvider() = default; + CoreProvider(std::shared_ptr controller); + virtual ~CoreProvider(); + + void addConsumer(CoreConsumer* consumer); + void removeConsumer(CoreConsumer* consumer); + + void setController(std::shared_ptr controller); + void setController(CoreController* controller); + CoreController* get() const; + inline CoreController* operator->() const { return get(); } + inline operator std::shared_ptr&() { return m_controller; } + inline operator bool() const { return get(); } + + void swap(std::shared_ptr& controller); + +private: + std::shared_ptr m_controller; + std::unordered_set m_consumers; +}; + +class CoreConsumer { + friend class CoreProvider; +public: + using ControllerCallback = std::function; + + CoreConsumer() = default; + CoreConsumer(const CoreConsumer& other); + CoreConsumer(CoreProvider* provider); + virtual ~CoreConsumer(); + + void setCoreProvider(CoreProvider* provider); + inline CoreProvider* coreProvider() const { return m_provider; } + CoreController* controller() const; + std::shared_ptr sharedController() const; + inline operator bool() const { return controller(); } + + ControllerCallback onControllerChanged; + +private: + void providerDestroyed(); + + CoreProvider* m_provider = nullptr; +}; + +} diff --git a/src/platform/qt/PopupManager.cpp b/src/platform/qt/PopupManager.cpp new file mode 100644 index 000000000..a91c3520a --- /dev/null +++ b/src/platform/qt/PopupManager.cpp @@ -0,0 +1,65 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "PopupManager.h" + +#include + +#include "CoreController.h" + +using namespace QGBA; +using CorePtr = PopupManagerBase::CorePtr; + +PopupManagerBase::PopupManagerBase(PopupManagerBase::Private* d) +: m_d(d) +{ + d->controller.onControllerChanged = [d]{ + d->updateConnections(); + d->notifyWindow(); + }; +} + +void PopupManagerBase::show() { + QWidget* w = construct(); + if (!w) { + return; + } + if (d()->isModal) { + w->setWindowModality(Qt::ApplicationModal); + } + w->show(); + w->activateWindow(); + w->raise(); +} + +QWidget* PopupManagerBase::construct() { + QWidget* w = d()->window(); + if (w) { + return w; + } + constructImpl(); + w = d()->window(); + if (w && d()->keepAlive) { + w->setAttribute(Qt::WA_DeleteOnClose); + } + d()->updateConnections(); + return w; +} + +void PopupManagerBase::Private::setProvider(CoreProvider* provider) { + controller.setCoreProvider(provider); + updateConnections(); +} + +void PopupManagerBase::Private::updateConnections() { + if (stopConnection) { + QObject::disconnect(stopConnection); + } + CoreController* c = controller.controller(); + QWidget* w = window(); + if (c && w) { + stopConnection = QObject::connect(c, &CoreController::stopping, w, &QWidget::close); + } +} diff --git a/src/platform/qt/PopupManager.h b/src/platform/qt/PopupManager.h new file mode 100644 index 000000000..0c71bd088 --- /dev/null +++ b/src/platform/qt/PopupManager.h @@ -0,0 +1,176 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "CoreConsumer.h" + +namespace QGBA { + +class CoreController; + +class PopupManagerBase { +public: + using CorePtr = std::shared_ptr; + + PopupManagerBase(const PopupManagerBase&) = default; + virtual ~PopupManagerBase() = default; + + QWidget* construct(); + void show(); + inline void operator()() { show(); } + +protected: + class Private : public QSharedData { + public: + Private(PopupManagerBase* pub) : pub(pub) {} + Private(const Private& other) = default; + + PopupManagerBase* pub; + bool isModal = false; + bool keepAlive = false; + CoreConsumer controller; + QMetaObject::Connection stopConnection; + + void setProvider(CoreProvider* provide); + void updateConnections(); + virtual QWidget* window() const = 0; + virtual void notifyWindow() = 0; + }; + + PopupManagerBase(Private* d); + + virtual void constructImpl() = 0; + + virtual Private* d() const { return m_d.data(); } + virtual Private* d() { return m_d.data(); } + +private: + QExplicitlySharedDataPointer m_d; +}; + +template +class PopupManager : public PopupManagerBase { + static_assert(std::is_convertible::value, "class must derive from QWidget"); + +protected: + class Private; + Private* d() const override { return static_cast(PopupManagerBase::d()); } + Private* d() override { return static_cast(PopupManagerBase::d()); } + + template + struct HasSetController { + using Pass = char; + using Fail = int; + struct Base { bool setController; }; + struct Test : T, Base {}; + template static Fail Check(decltype(U::setController)*); + template static Pass Check(U*); + static constexpr bool value = sizeof(Check(nullptr)) == sizeof(Pass); + }; + +public: + PopupManager() : PopupManagerBase(new Private(this)) {} + PopupManager(const PopupManager&) = default; + + bool isNull() const { return d()->ptr.isNull(); } + WINDOW* operator->() const { return d()->ptr; } + WINDOW& operator*() const { return &d()->ptr; } + operator WINDOW*() const { return d()->ptr; } + + PopupManager& withController(CoreProvider& provider) { d()->setProvider(&provider); return *this; } + PopupManager& setModal(bool modal) { d()->isModal = modal; return *this; } + PopupManager& setKeepAlive(bool keepAlive) { d()->keepAlive = keepAlive; return *this; } + PopupManager& constructWith(const std::function& ctor) { d()->construct = ctor; return *this; } + + template + PopupManager& constructWith(Args... args) { + d()->construct = makeConstruct(args...); + return *this; + } + +protected: + virtual void constructImpl() override { + Private* d = this->d(); + if (!d->construct) { + qWarning("No valid constructor specified for popup"); + return; + } + WINDOW* w = d->construct(); + if (!w) { + qWarning("Constructor did not return a window"); + return; + } + d->ptr = w; + } + + template + typename std::enable_if::value, std::function>::type makeConstruct(Args... args) { + return [=]() -> T* { return new T(d()->controller.sharedController(), args...); }; + } + + template + typename std::enable_if::value, std::function>::type makeConstruct(Args... args) { + return [=]() -> T* { return new T(args...); }; + } + + class Private : public PopupManagerBase::Private { + template + struct Ctors { + static constexpr bool useController = std::is_constructible::value; + static constexpr bool useDefault = !useController && std::is_default_constructible::value; + static constexpr bool noDefault = !useController && !useDefault; + }; + + public: + template + Private(PopupManagerBase* pub, typename std::enable_if::useDefault>::type* = 0) + : PopupManagerBase::Private(pub), construct([]{ return new WINDOW(); }) {} + + template + Private(PopupManagerBase* pub, typename std::enable_if::useController>::type* = 0) + : PopupManagerBase::Private(pub), construct([this]{ return new WINDOW(controller.sharedController()); }) {} + + template + Private(PopupManagerBase* pub, typename std::enable_if::noDefault>::type* = 0) + : PopupManagerBase::Private(pub) {} + + ~Private() { + if (ptr && !keepAlive) { + ptr->close(); + ptr->deleteLater(); + } + } + + virtual QWidget* window() const override { return ptr.data(); } + + virtual void notifyWindow() override { notifyWindow(); } + + template + typename std::enable_if::value>::type notifyWindow() { + if (ptr) { + ptr->setController(controller.sharedController()); + } + } + + template + typename std::enable_if::value>::type notifyWindow() { + // Nothing to do + } + + QPointer ptr; + std::function construct; + }; +}; + +} diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 155431cb1..5071b0cd3 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1724,7 +1724,8 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addMenu(tr("&Tools"), "tools"); m_actions.addAction(tr("View &logs..."), "viewLogs", openNamedTView(&m_logView, true, &m_log, this), "tools"); - m_actions.addAction(tr("Game &overrides..."), "overrideWindow", openNamedControllerTView(&m_overrideView, true, m_config, this), "tools"); + m_overrideView.withController(m_controller).constructWith(m_config, this); + m_actions.addAction(tr("Game &overrides..."), "overrideWindow", m_overrideView, "tools"); m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", openNamedControllerTView(&m_sensorView, true, &m_inputController, this), "tools"); addGameAction(tr("&Cheats..."), "cheatsWindow", openNamedControllerTView(&m_cheatsView, false), "tools"); @@ -2155,7 +2156,7 @@ void Window::setController(CoreController* controller, const QString& fname) { reloadDisplayDriver(); } - m_controller = std::shared_ptr(controller); + m_controller.setController(controller); m_controller->setInputController(&m_inputController); m_controller->setLogger(&m_log); @@ -2234,10 +2235,6 @@ void Window::setController(CoreController* controller, const QString& fname) { m_sensorView->setController(m_controller); } - if (m_overrideView) { - m_overrideView->setController(m_controller); - } - if (!m_pendingPatch.isEmpty()) { m_controller->loadPatch(m_pendingPatch); m_pendingPatch = QString(); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 1af7ac0f0..b3a96895b 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -22,6 +22,8 @@ #include "InputController.h" #include "LoadSaveState.h" #include "LogController.h" +#include "OverrideView.h" +#include "PopupManager.h" #include "SettingsView.h" #ifdef ENABLE_SCRIPTING #include "scripting/ScriptingController.h" @@ -194,7 +196,7 @@ private: QString getFiltersArchive() const; CoreManager* m_manager; - std::shared_ptr m_controller; + CoreProvider m_controller; std::unique_ptr m_audioProcessor; std::unique_ptr m_display; @@ -245,7 +247,7 @@ private: bool m_multiActive = true; int m_playerId; - QPointer m_overrideView; + PopupManager m_overrideView; QPointer m_sensorView; QPointer m_dolphinView; QPointer m_frameView;