PopupManager

This commit is contained in:
Adam Higerd 2025-05-25 20:40:10 -05:00
parent f48655e2f3
commit 7fd40044bc
7 changed files with 411 additions and 8 deletions

View File

@ -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

View File

@ -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<CoreController> controller) {
setController(controller);
}
CoreProvider::~CoreProvider() {
for (CoreConsumer* consumer : m_consumers) {
consumer->m_provider = nullptr;
}
}
void CoreProvider::setController(std::shared_ptr<CoreController> controller) {
if (m_controller) {
// Disconnect all signals from old controller
QObject::disconnect(m_controller.get(), nullptr, this, nullptr);
}
std::shared_ptr<CoreController> 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<CoreController>(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<CoreController>& 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<CoreController> CoreConsumer::sharedController() const {
if (!m_provider) {
return nullptr;
}
return *m_provider;
}
void CoreConsumer::providerDestroyed() {
m_provider = nullptr;
}

View File

@ -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 <QObject>
#include <memory>
#include <unordered_set>
namespace QGBA {
class CoreController;
class CoreConsumer;
class CoreProvider : public QObject {
public:
CoreProvider() = default;
CoreProvider(std::shared_ptr<CoreController> controller);
virtual ~CoreProvider();
void addConsumer(CoreConsumer* consumer);
void removeConsumer(CoreConsumer* consumer);
void setController(std::shared_ptr<CoreController> controller);
void setController(CoreController* controller);
CoreController* get() const;
inline CoreController* operator->() const { return get(); }
inline operator std::shared_ptr<CoreController>&() { return m_controller; }
inline operator bool() const { return get(); }
void swap(std::shared_ptr<CoreController>& controller);
private:
std::shared_ptr<CoreController> m_controller;
std::unordered_set<CoreConsumer*> m_consumers;
};
class CoreConsumer {
friend class CoreProvider;
public:
using ControllerCallback = std::function<void()>;
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<CoreController> sharedController() const;
inline operator bool() const { return controller(); }
ControllerCallback onControllerChanged;
private:
void providerDestroyed();
CoreProvider* m_provider = nullptr;
};
}

View File

@ -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 <QDialog>
#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);
}
}

View File

@ -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 <QExplicitlySharedDataPointer>
#include <QPointer>
#include <QSharedData>
#include <functional>
#include <memory>
#include <type_traits>
#include "CoreConsumer.h"
namespace QGBA {
class CoreController;
class PopupManagerBase {
public:
using CorePtr = std::shared_ptr<CoreController>;
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<Private> m_d;
};
template <class WINDOW>
class PopupManager : public PopupManagerBase {
static_assert(std::is_convertible<WINDOW*, QWidget*>::value, "class must derive from QWidget");
protected:
class Private;
Private* d() const override { return static_cast<Private*>(PopupManagerBase::d()); }
Private* d() override { return static_cast<Private*>(PopupManagerBase::d()); }
template <typename T>
struct HasSetController {
using Pass = char;
using Fail = int;
struct Base { bool setController; };
struct Test : T, Base {};
template <typename U> static Fail Check(decltype(U::setController)*);
template <typename U> static Pass Check(U*);
static constexpr bool value = sizeof(Check<Test>(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<WINDOW*()>& ctor) { d()->construct = ctor; return *this; }
template <typename... Args>
PopupManager& constructWith(Args... args) {
d()->construct = makeConstruct<WINDOW, Args...>(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 T, typename... Args>
typename std::enable_if<std::is_constructible<T, CorePtr, Args...>::value, std::function<T*()>>::type makeConstruct(Args... args) {
return [=]() -> T* { return new T(d()->controller.sharedController(), args...); };
}
template <typename T, typename... Args>
typename std::enable_if<!std::is_constructible<T, CorePtr, Args...>::value, std::function<T*()>>::type makeConstruct(Args... args) {
return [=]() -> T* { return new T(args...); };
}
class Private : public PopupManagerBase::Private {
template <class T = WINDOW>
struct Ctors {
static constexpr bool useController = std::is_constructible<T, CorePtr>::value;
static constexpr bool useDefault = !useController && std::is_default_constructible<T>::value;
static constexpr bool noDefault = !useController && !useDefault;
};
public:
template<class T = WINDOW>
Private(PopupManagerBase* pub, typename std::enable_if<Ctors<T>::useDefault>::type* = 0)
: PopupManagerBase::Private(pub), construct([]{ return new WINDOW(); }) {}
template<class T = WINDOW>
Private(PopupManagerBase* pub, typename std::enable_if<Ctors<T>::useController>::type* = 0)
: PopupManagerBase::Private(pub), construct([this]{ return new WINDOW(controller.sharedController()); }) {}
template<class T = WINDOW>
Private(PopupManagerBase* pub, typename std::enable_if<Ctors<T>::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<WINDOW>(); }
template<class T>
typename std::enable_if<HasSetController<T>::value>::type notifyWindow() {
if (ptr) {
ptr->setController(controller.sharedController());
}
}
template<class T>
typename std::enable_if<!HasSetController<T>::value>::type notifyWindow() {
// Nothing to do
}
QPointer<WINDOW> ptr;
std::function<WINDOW*()> construct;
};
};
}

View File

@ -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<CoreController>(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();

View File

@ -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<CoreController> m_controller;
CoreProvider m_controller;
std::unique_ptr<AudioProcessor> m_audioProcessor;
std::unique_ptr<QGBA::Display> m_display;
@ -245,7 +247,7 @@ private:
bool m_multiActive = true;
int m_playerId;
QPointer<OverrideView> m_overrideView;
PopupManager<OverrideView> m_overrideView;
QPointer<SensorView> m_sensorView;
QPointer<DolphinConnector> m_dolphinView;
QPointer<FrameView> m_frameView;