From b17cceaeafa6ec37bd2a478f86e0ca64890bf996 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Jun 2020 23:50:18 -0400 Subject: [PATCH] Tidies up and makes a failing attempt at SDI improvements. --- OSBindings/Qt/ClockSignal.pro | 2 +- OSBindings/Qt/mainwindow.cpp | 336 ++++++++++++++++++---------------- OSBindings/Qt/mainwindow.h | 8 +- 3 files changed, 186 insertions(+), 160 deletions(-) diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index 57e7ccb10..656eeac4f 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -1,4 +1,4 @@ -QT += core gui multimedia +QT += core gui multimedia gamepad greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 2c1040e25..3b038dd59 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -8,155 +8,6 @@ #include "../../Numeric/CRC.hpp" -/* - General Qt implementation notes: - - * it seems like Qt doesn't offer a way to constrain the aspect ratio of a view by constraining - the size of the window (i.e. you can use a custom layout to constrain a view, but that won't - affect the window, so isn't useful for this project). Therefore the emulation window resizes freely. -*/ - -MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { - init(); - setVisibleWidgetSet(WidgetSet::MachinePicker); -} - -MainWindow::MainWindow(const QString &fileName) { - init(); - launchFile(fileName); -} - -void MainWindow::init() { - qApp->installEventFilter(this); - - ui = std::make_unique(); - ui->setupUi(this); - romRequestBaseText = ui->missingROMsBox->toPlainText(); - - createActions(); - restoreSelections(); - - timer = std::make_unique(this); -} - -void MainWindow::createActions() { - // Create a file menu. - QMenu *const fileMenu = menuBar()->addMenu(tr("&File")); - - // Add file option: 'New' - QAction *const newAct = new QAction(tr("&New"), this); - newAct->setShortcuts(QKeySequence::New); - newAct->setStatusTip(tr("Create a new file")); - connect(newAct, &QAction::triggered, this, &MainWindow::newFile); - fileMenu->addAction(newAct); - - // Add file option: 'Open...' - QAction *const openAct = new QAction(tr("&Open..."), this); - openAct->setShortcuts(QKeySequence::Open); - openAct->setStatusTip(tr("Open an existing file")); - connect(openAct, &QAction::triggered, this, &MainWindow::open); - fileMenu->addAction(openAct); - - // Add a separator and then an 'Insert...'. - fileMenu->addSeparator(); - QAction *const insertAct = new QAction(tr("&Insert..."), this); - insertAct->setStatusTip(tr("Open an existing file")); - connect(insertAct, &QAction::triggered, this, &MainWindow::insert); - fileMenu->addAction(insertAct); - - // Add Help menu, with an 'About...' option. - QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); - QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::about); - aboutAct->setStatusTip(tr("Show the application's About box")); - - // Link up the start machine button. - connect(ui->startMachineButton, &QPushButton::clicked, this, &MainWindow::startMachine); -} - -void MainWindow::open() { - Settings settings; - - // Use the Settings to get a default open path; write it back afterwards. - QString fileName = QFileDialog::getOpenFileName(this, tr("Open..."), settings.value("openPath").toString()); - if(!fileName.isEmpty()) { - settings.setValue("openPath", QFileInfo(fileName).absoluteDir().path()); - - // My understanding of SDI: if a file was opened for a 'vacant' window, launch it directly there; - // otherwise create a new window for it. - if(machine) { - MainWindow *const other = new MainWindow(fileName); - other->tile(this); - other->show(); - } else { - launchFile(fileName); - } - } -} - -void MainWindow::insert() { -} - -void MainWindow::launchFile(const QString &fileName) { - targets = Analyser::Static::GetTargets(fileName.toStdString()); - if(!targets.empty()) { - launchMachine(); - } -} - -void MainWindow::newFile() { - MainWindow *other = new MainWindow; - other->tile(this); - other->show(); -} - -void MainWindow::tile(const QMainWindow *previous) { - // This entire function is essentially verbatim from the Qt SDI example. - if (!previous) - return; - - int topFrameWidth = previous->geometry().top() - previous->pos().y(); - if (!topFrameWidth) - topFrameWidth = 40; - - const QPoint pos = previous->pos() + 2 * QPoint(topFrameWidth, topFrameWidth); - if (screen()->availableGeometry().contains(rect().bottomRight() + pos)) - move(pos); -} - - -void MainWindow::about() { - QMessageBox::about(this, tr("About Clock Signal"), - tr( "

Clock Signal is an emulator of various platforms.

" - - "

This emulator is offered under the MIT licence; its source code " - "is available from GitHub.

" - - "

This port is experimental, especially with regard to latency; " - "please don't hesitate to provide feedback, " - "by email or via the " - "GitHub issue tracker.

" - )); -} - -MainWindow::~MainWindow() { - // Stop the timer; stopping this first ensures the machine won't attempt - // to write to the audioOutput while it is being shut down. - timer.reset(); - - // Stop the audio output, and its thread. - if(audioOutput) { - audioThread.performAsync([this] { - audioOutput->stop(); - }); - audioThread.stop(); - } - - // Store the current user selections. - storeSelections(); -} - -// MARK: Machine launch. - namespace { std::unique_ptr> fileContentsAndClose(FILE *file) { @@ -177,6 +28,172 @@ std::unique_ptr> fileContentsAndClose(FILE *file) { } +/* + General Qt implementation notes: + + * it seems like Qt doesn't offer a way to constrain the aspect ratio of a view by constraining + the size of the window (i.e. you can use a custom layout to constrain a view, but that won't + affect the window, so isn't useful for this project). Therefore the emulation window resizes freely. +*/ + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + init(); + setVisibleWidgetSet(WidgetSet::MachinePicker); +} + +MainWindow::MainWindow(const QString &fileName) { + init(); + launchFile(fileName); +} + +MainWindow::~MainWindow() { + // Stop the timer; stopping this first ensures the machine won't attempt + // to write to the audioOutput while it is being shut down. + timer.reset(); + + // Stop the audio output, and its thread. + if(audioOutput) { + audioThread.performAsync([this] { + audioOutput->stop(); + }); + audioThread.stop(); + } + + // Store the current user selections. + storeSelections(); + + // SDI behaviour, which may or may not be normal (?): if the user is closing a + // final window, and it contains a machine, send them back to the machine picker. + // i.e. assume they were closing that document, not the application. + --mainWindowCount; + if(machine && !mainWindowCount) { + MainWindow *const other = new MainWindow; + other->show(); + } +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if(mainWindowCount == 1 && machine) { + qDebug() << "close, yah"; + } + QMainWindow::closeEvent(event); +} + +void MainWindow::init() { + ++mainWindowCount; + qApp->installEventFilter(this); + + ui = std::make_unique(); + ui->setupUi(this); + romRequestBaseText = ui->missingROMsBox->toPlainText(); + + createActions(); + restoreSelections(); + + timer = std::make_unique(this); +} + +void MainWindow::createActions() { + // Create a file menu. + QMenu *const fileMenu = menuBar()->addMenu(tr("&File")); + + // Add file option: 'New' + QAction *const newAct = new QAction(tr("&New"), this); + newAct->setShortcuts(QKeySequence::New); + newAct->setStatusTip(tr("Create a new file")); + connect(newAct, &QAction::triggered, this, [this] { + storeSelections(); + + MainWindow *other = new MainWindow; + other->tile(this); + other->show(); + }); + fileMenu->addAction(newAct); + + // Add file option: 'Open...' + QAction *const openAct = new QAction(tr("&Open..."), this); + openAct->setShortcuts(QKeySequence::Open); + openAct->setStatusTip(tr("Open an existing file")); + connect(openAct, &QAction::triggered, this, [this] { + const QString fileName = getFilename("Open..."); + if(!fileName.isEmpty()) { + // My understanding of SDI: if a file was opened for a 'vacant' window, launch it directly there; + // otherwise create a new window for it. + if(machine) { + MainWindow *const other = new MainWindow(fileName); + other->tile(this); + other->show(); + } else { + launchFile(fileName); + } + } + }); + fileMenu->addAction(openAct); + + // Add a separator and then an 'Insert...'. + fileMenu->addSeparator(); + QAction *const insertAct = new QAction(tr("&Insert..."), this); + insertAct->setStatusTip(tr("Open an existing file")); + connect(insertAct, &QAction::triggered, this, [] { + // TODO. + }); + fileMenu->addAction(insertAct); + + // Add Help menu, with an 'About...' option. + QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); + QAction *aboutAct = helpMenu->addAction(tr("&About"), this, [this] { + QMessageBox::about(this, tr("About Clock Signal"), + tr( "

Clock Signal is an emulator of various platforms.

" + + "

This emulator is offered under the MIT licence; its source code " + "is available from GitHub.

" + + "

This port is experimental, especially with regard to latency; " + "please don't hesitate to provide feedback, " + "by email or via the " + "GitHub issue tracker.

" + )); + }); + aboutAct->setStatusTip(tr("Show the application's About box")); + + // Link up the start machine button. + connect(ui->startMachineButton, &QPushButton::clicked, this, &MainWindow::startMachine); +} + +QString MainWindow::getFilename(const char *title) { + Settings settings; + + // Use the Settings to get a default open path; write it back afterwards. + QString fileName = QFileDialog::getOpenFileName(this, tr(title), settings.value("openPath").toString()); + if(!fileName.isEmpty()) { + settings.setValue("openPath", QFileInfo(fileName).absoluteDir().path()); + } + return fileName; +} + +void MainWindow::launchFile(const QString &fileName) { + targets = Analyser::Static::GetTargets(fileName.toStdString()); + if(!targets.empty()) { + launchMachine(); + } +} + +void MainWindow::tile(const QMainWindow *previous) { + // This entire function is essentially verbatim from the Qt SDI example. + if (!previous) + return; + + int topFrameWidth = previous->geometry().top() - previous->pos().y(); + if (!topFrameWidth) + topFrameWidth = 40; + + const QPoint pos = previous->pos() + 2 * QPoint(topFrameWidth, topFrameWidth); + if (screen()->availableGeometry().contains(rect().bottomRight() + pos)) + move(pos); +} + +// MARK: Machine launch. + void MainWindow::launchMachine() { const QStringList appDataLocations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); missingRoms.clear(); @@ -309,9 +326,16 @@ void MainWindow::dropEvent(QDropEvent* event) { event->accept(); switch(uiPhase) { - case UIPhase::NoFileSelected: + case UIPhase::NoFileSelected: { // Treat exactly as a File -> Open... . - break; + // TODO: permit multiple files dropped at once. + const auto fileName = event->mimeData()->urls()[0].toLocalFile(); + launchFile(fileName); + } break; + + case UIPhase::RunningMachine: { + // Attempt to insert into the running machine. + } break; case UIPhase::RequestingROMs: { // Attempt to match up the dragged files to the requested ROM list; @@ -351,10 +375,6 @@ void MainWindow::dropEvent(QDropEvent* event) { if(foundROM) launchMachine(); } break; - - case UIPhase::RunningMachine: - // Attempt to insert into the running machine. - break; } } @@ -370,6 +390,9 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { } } break; + case QEvent::Close: + break; + default: break; } @@ -670,4 +693,7 @@ void MainWindow::storeSelections() { } void MainWindow::restoreSelections() { + Settings settings; + + ui->machineSelectionTabs->setCurrentIndex(settings.value("machineSelection").toInt()); } diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index a9846b8db..d45561039 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -68,10 +68,6 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat void setVisibleWidgetSet(WidgetSet); private slots: - void open(); - void newFile(); - void about(); - void insert(); void startMachine(); private: @@ -94,6 +90,10 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat void init(); void tile(const QMainWindow *previous); + QString getFilename(const char *title); + + void closeEvent(QCloseEvent *event) override; + static inline int mainWindowCount = 0; }; #endif // MAINWINDOW_H