mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Tidies up and makes a failing attempt at SDI improvements.
This commit is contained in:
parent
782a62585e
commit
b17cceaeaf
@ -1,4 +1,4 @@
|
||||
QT += core gui multimedia
|
||||
QT += core gui multimedia gamepad
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
|
@ -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::MainWindow>();
|
||||
ui->setupUi(this);
|
||||
romRequestBaseText = ui->missingROMsBox->toPlainText();
|
||||
|
||||
createActions();
|
||||
restoreSelections();
|
||||
|
||||
timer = std::make_unique<Timer>(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( "<p>Clock Signal is an emulator of various platforms.</p>"
|
||||
|
||||
"<p>This emulator is offered under the MIT licence; its source code "
|
||||
"is available from <a href=\"https://github.com/tomharte/CLK\">GitHub</a>.</p>"
|
||||
|
||||
"<p>This port is experimental, especially with regard to latency; "
|
||||
"please don't hesitate to provide feedback, "
|
||||
"<a href=\"mailto:thomas.harte@gmail.com\">by email</a> or via the "
|
||||
"<a href=\"https://github.com/tomharte/CLK/issues\">GitHub issue tracker</a>.</p>"
|
||||
));
|
||||
}
|
||||
|
||||
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<std::vector<uint8_t>> fileContentsAndClose(FILE *file) {
|
||||
@ -177,6 +28,172 @@ std::unique_ptr<std::vector<uint8_t>> 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::MainWindow>();
|
||||
ui->setupUi(this);
|
||||
romRequestBaseText = ui->missingROMsBox->toPlainText();
|
||||
|
||||
createActions();
|
||||
restoreSelections();
|
||||
|
||||
timer = std::make_unique<Timer>(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( "<p>Clock Signal is an emulator of various platforms.</p>"
|
||||
|
||||
"<p>This emulator is offered under the MIT licence; its source code "
|
||||
"is available from <a href=\"https://github.com/tomharte/CLK\">GitHub</a>.</p>"
|
||||
|
||||
"<p>This port is experimental, especially with regard to latency; "
|
||||
"please don't hesitate to provide feedback, "
|
||||
"<a href=\"mailto:thomas.harte@gmail.com\">by email</a> or via the "
|
||||
"<a href=\"https://github.com/tomharte/CLK/issues\">GitHub issue tracker</a>.</p>"
|
||||
));
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user