368 lines
13 KiB
C++
368 lines
13 KiB
C++
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* 86Box VM manager add machine wizard
|
|
*
|
|
*
|
|
*
|
|
* Authors: cold-brewed
|
|
*
|
|
* Copyright 2024 cold-brewed
|
|
*/
|
|
|
|
#include <QApplication>
|
|
#include <QDebug>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QPushButton>
|
|
#include <QStyle>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "qt_vmmanager_addmachine.hpp"
|
|
|
|
extern "C" {
|
|
#include <86box/86box.h>
|
|
}
|
|
|
|
// Implementation note: There are several classes in this file:
|
|
// One for the main Wizard class and one for each page of the wizard
|
|
|
|
VMManagerAddMachine::
|
|
VMManagerAddMachine(QWidget *parent) : QWizard(parent)
|
|
{
|
|
setPage(Page_Intro, new IntroPage);
|
|
setPage(Page_WithExistingConfig, new WithExistingConfigPage);
|
|
setPage(Page_NameAndLocation, new NameAndLocationPage);
|
|
setPage(Page_Conclusion, new ConclusionPage);
|
|
|
|
// Need to create a better image
|
|
// QPixmap originalPixmap(":/assets/86box.png");
|
|
// QPixmap scaledPixmap = originalPixmap.scaled(150, 150, Qt::KeepAspectRatio);
|
|
QPixmap wizardPixmap(":/assets/86box-wizard.png");
|
|
|
|
#ifndef Q_OS_MACOS
|
|
setWizardStyle(ModernStyle);
|
|
// setPixmap(LogoPixmap, scaledPixmap);
|
|
// setPixmap(LogoPixmap, wizardPixmap);
|
|
// setPixmap(WatermarkPixmap, scaledPixmap);
|
|
setPixmap(WatermarkPixmap, wizardPixmap);
|
|
#else
|
|
// macos
|
|
// setPixmap(BackgroundPixmap, scaledPixmap);
|
|
setPixmap(BackgroundPixmap, wizardPixmap);
|
|
#endif
|
|
|
|
// Wizard wants to resize based on image. This keeps the size
|
|
setMinimumSize(size());
|
|
setOption(HaveHelpButton, true);
|
|
// setPixmap(LogoPixmap, QPixmap(":/settings/qt/icons/86Box-gray.ico"));
|
|
|
|
connect(this, &QWizard::helpRequested, this, &VMManagerAddMachine::showHelp);
|
|
|
|
setWindowTitle(tr("Add new system wizard"));
|
|
}
|
|
|
|
void
|
|
VMManagerAddMachine::showHelp()
|
|
{
|
|
// TBD
|
|
static QString lastHelpMessage;
|
|
|
|
QString message;
|
|
|
|
// Help will depend on the current page
|
|
switch (currentId()) {
|
|
case Page_Intro:
|
|
message = tr("This is the into page.");
|
|
break;
|
|
default:
|
|
message = tr("No help has been added yet, you're on your own.");
|
|
break;
|
|
}
|
|
|
|
if (lastHelpMessage == message) {
|
|
message = tr("Did you click help twice?");
|
|
}
|
|
|
|
QMessageBox::information(this, tr("Add new system wizard help"), message);
|
|
lastHelpMessage = message;
|
|
}
|
|
|
|
IntroPage::
|
|
IntroPage(QWidget *parent)
|
|
{
|
|
setTitle(tr("Introduction"));
|
|
|
|
setPixmap(QWizard::WatermarkPixmap, QPixmap(":/assets/qt/assets/86box.png"));
|
|
|
|
topLabel = new QLabel(tr("This will help you add a new system to 86Box."));
|
|
// topLabel = new QLabel(tr("This will help you add a new system to 86Box.\n\n Choose \"New configuration\" if you'd like to create a new machine.\n\nChoose \"Use existing configuration\" if you'd like to paste in an existing configuration from elsewhere."));
|
|
topLabel->setWordWrap(true);
|
|
|
|
newConfigRadioButton = new QRadioButton(tr("New configuration"));
|
|
// auto newDescription = new QLabel(tr("Choose this option to start with a fresh configuration."));
|
|
existingConfigRadioButton = new QRadioButton(tr("Use existing configuraion"));
|
|
// auto existingDescription = new QLabel(tr("Use this option if you'd like to paste in the configuration file from an existing system."));
|
|
newConfigRadioButton->setChecked(true);
|
|
|
|
const auto layout = new QVBoxLayout();
|
|
layout->addWidget(topLabel);
|
|
layout->addWidget(newConfigRadioButton);
|
|
// layout->addWidget(newDescription);
|
|
layout->addWidget(existingConfigRadioButton);
|
|
// layout->addWidget(existingDescription);
|
|
|
|
setLayout(layout);
|
|
}
|
|
|
|
int
|
|
IntroPage::nextId() const
|
|
{
|
|
if (newConfigRadioButton->isChecked()) {
|
|
return VMManagerAddMachine::Page_NameAndLocation;
|
|
} else {
|
|
return VMManagerAddMachine::Page_WithExistingConfig;
|
|
}
|
|
}
|
|
|
|
WithExistingConfigPage::
|
|
WithExistingConfigPage(QWidget *parent)
|
|
{
|
|
setTitle(tr("Use existing configuration"));
|
|
|
|
const auto topLabel = new QLabel(tr("Paste the contents of the existing configuration file into the box below."));
|
|
topLabel->setWordWrap(true);
|
|
|
|
existingConfiguration = new QPlainTextEdit();
|
|
connect(existingConfiguration, &QPlainTextEdit::textChanged, this, &WithExistingConfigPage::completeChanged);
|
|
registerField("existingConfiguration*", this, "configuration");
|
|
|
|
const auto layout = new QVBoxLayout();
|
|
layout->addWidget(topLabel);
|
|
layout->addWidget(existingConfiguration);
|
|
const auto loadFileButton = new QPushButton();
|
|
const auto loadFileLabel = new QLabel(tr("Load configuration from file"));
|
|
const auto hLayout = new QHBoxLayout();
|
|
loadFileButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_FileIcon));
|
|
loadFileButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
connect(loadFileButton, &QPushButton::clicked, this, &WithExistingConfigPage::chooseExistingConfigFile);
|
|
hLayout->addWidget(loadFileButton);
|
|
hLayout->addWidget(loadFileLabel);
|
|
layout->addLayout(hLayout);
|
|
setLayout(layout);
|
|
}
|
|
|
|
void
|
|
WithExistingConfigPage::chooseExistingConfigFile()
|
|
{
|
|
// TODO: FIXME: This is using the CLI arg and needs to instead use a proper variable
|
|
const auto startDirectory = QString(vmm_path);
|
|
const auto selectedConfigFile = QFileDialog::getOpenFileName(this, tr("Choose configuration file"),
|
|
startDirectory,
|
|
tr("86Box configuration files (86box.cfg)"));
|
|
// Empty value means the dialog was canceled
|
|
if (!selectedConfigFile.isEmpty()) {
|
|
QFile configFile(selectedConfigFile);
|
|
if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
QMessageBox::critical(this, tr("Configuration read failed"), tr("Unable to open the selected configuration file for reading: %1").arg(configFile.errorString()));
|
|
return;
|
|
}
|
|
const QString configFileContents = configFile.readAll();
|
|
existingConfiguration->setPlainText(configFileContents);
|
|
configFile.close();
|
|
emit completeChanged();
|
|
}
|
|
}
|
|
|
|
QString
|
|
WithExistingConfigPage::configuration() const
|
|
{
|
|
return existingConfiguration->toPlainText();
|
|
}
|
|
void
|
|
WithExistingConfigPage::setConfiguration(const QString &configuration)
|
|
{
|
|
if (configuration != existingConfiguration->toPlainText()) {
|
|
existingConfiguration->setPlainText(configuration);
|
|
emit configurationChanged(configuration);
|
|
}
|
|
}
|
|
|
|
int
|
|
WithExistingConfigPage::nextId() const
|
|
{
|
|
return VMManagerAddMachine::Page_NameAndLocation;
|
|
}
|
|
|
|
bool
|
|
WithExistingConfigPage::isComplete() const
|
|
{
|
|
return !existingConfiguration->toPlainText().isEmpty();
|
|
}
|
|
|
|
NameAndLocationPage::
|
|
NameAndLocationPage(QWidget *parent)
|
|
{
|
|
setTitle(tr("System name and location"));
|
|
|
|
#if defined(_WIN32)
|
|
dirValidate = QRegularExpression(R"(^[^\\/:*?"<>|]+$)");
|
|
#elif defined(__APPLE__)
|
|
dirValidate = QRegularExpression(R"(^[^/:]+$)");
|
|
#else
|
|
dirValidate = QRegularExpression(R"(^[^/]+$)");
|
|
#endif
|
|
|
|
const auto topLabel = new QLabel(tr("Enter the name of the system and choose the location"));
|
|
topLabel->setWordWrap(true);
|
|
|
|
const auto chooseDirectoryButton = new QPushButton();
|
|
chooseDirectoryButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_DirIcon));
|
|
|
|
const auto systemNameLabel = new QLabel(tr("System Name"));
|
|
systemName = new QLineEdit();
|
|
// Special event filter to override enter key
|
|
systemName->installEventFilter(this);
|
|
registerField("systemName*", systemName);
|
|
systemNameValidation = new QLabel();
|
|
|
|
const auto systemLocationLabel = new QLabel(tr("System Location"));
|
|
systemLocation = new QLineEdit();
|
|
// TODO: FIXME: This is using the CLI arg and needs to instead use a proper variable
|
|
systemLocation->setText(QDir::toNativeSeparators(vmm_path));
|
|
registerField("systemLocation*", systemLocation);
|
|
systemLocationValidation = new QLabel();
|
|
systemLocationValidation->setWordWrap(true);
|
|
|
|
const auto layout = new QGridLayout();
|
|
layout->addWidget(topLabel, 0, 0, 1, -1);
|
|
// Spacer row
|
|
layout->setRowMinimumHeight(1, 20);
|
|
layout->addWidget(systemNameLabel, 2, 0);
|
|
layout->addWidget(systemName, 2, 1);
|
|
// Validation text, appears only as necessary
|
|
layout->addWidget(systemNameValidation, 3, 0, 1, -1);
|
|
// Set height on validation because it may not always be present
|
|
layout->setRowMinimumHeight(3, 20);
|
|
|
|
// Another spacer
|
|
layout->setRowMinimumHeight(4, 20);
|
|
layout->addWidget(systemLocationLabel, 5, 0);
|
|
layout->addWidget(systemLocation, 5, 1);
|
|
layout->addWidget(chooseDirectoryButton, 5, 2);
|
|
// Validation text
|
|
layout->addWidget(systemLocationValidation, 6, 0, 1, -1);
|
|
layout->setRowMinimumHeight(6, 20);
|
|
|
|
setLayout(layout);
|
|
|
|
|
|
connect(chooseDirectoryButton, &QPushButton::clicked, this, &NameAndLocationPage::chooseDirectoryLocation);
|
|
}
|
|
|
|
int
|
|
NameAndLocationPage::nextId() const
|
|
{
|
|
return VMManagerAddMachine::Page_Conclusion;
|
|
}
|
|
|
|
void
|
|
NameAndLocationPage::chooseDirectoryLocation()
|
|
{
|
|
// TODO: FIXME: This is pulling in the CLI directory! Needs to be set properly elsewhere
|
|
const auto directory = QFileDialog::getExistingDirectory(this, "Choose directory", QDir(vmm_path).path());
|
|
systemLocation->setText(QDir::toNativeSeparators(directory));
|
|
emit completeChanged();
|
|
}
|
|
bool
|
|
NameAndLocationPage::isComplete() const
|
|
{
|
|
bool nameValid = false;
|
|
bool locationValid = false;
|
|
// return true if complete
|
|
if (systemName->text().isEmpty()) {
|
|
systemNameValidation->setText(tr("Please enter a system name"));
|
|
} else if (!systemName->text().contains(dirValidate)) {
|
|
systemNameValidation->setText(tr("System name cannot contain certain characters"));
|
|
} else if (const QDir newDir = QDir::cleanPath(systemLocation->text() + "/" + systemName->text()); newDir.exists()) {
|
|
systemNameValidation->setText(tr("System name already exists"));
|
|
} else {
|
|
systemNameValidation->clear();
|
|
nameValid = true;
|
|
}
|
|
|
|
if (systemLocation->text().isEmpty()) {
|
|
systemLocationValidation->setText(tr("Please enter a directory for the system"));
|
|
} else if (const auto dir = QDir(systemLocation->text()); !dir.exists()) {
|
|
systemLocationValidation->setText(tr("Directory does not exist"));
|
|
} else {
|
|
systemLocationValidation->setText("A new directory for the system will be created in the selected directory above");
|
|
locationValid = true;
|
|
}
|
|
|
|
return nameValid && locationValid;
|
|
}
|
|
bool
|
|
NameAndLocationPage::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
// Override the enter key to hit the next wizard button
|
|
// if the validator (isComplete) is satisfied
|
|
if (event->type() == QEvent::KeyPress) {
|
|
const auto keyEvent = dynamic_cast<QKeyEvent*>(event);
|
|
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
|
|
// Only advance if the validator is satisfied (isComplete)
|
|
if(const auto wizard = qobject_cast<QWizard*>(this->wizard())) {
|
|
if (wizard->currentPage()->isComplete()) {
|
|
wizard->next();
|
|
}
|
|
}
|
|
// Discard the key event
|
|
return true;
|
|
}
|
|
}
|
|
return QWizardPage::eventFilter(watched, event);
|
|
}
|
|
|
|
ConclusionPage::
|
|
ConclusionPage(QWidget *parent)
|
|
{
|
|
setTitle(tr("Complete"));
|
|
|
|
topLabel = new QLabel(tr("The wizard will now launch the configuration for the new system."));
|
|
topLabel->setWordWrap(true);
|
|
|
|
const auto systemNameLabel = new QLabel(tr("System name:"));
|
|
systemNameLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
systemName = new QLabel();
|
|
const auto systemLocationLabel = new QLabel(tr("System location:"));
|
|
systemLocationLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
systemLocation = new QLabel();
|
|
|
|
const auto layout = new QGridLayout();
|
|
layout->addWidget(topLabel, 0, 0, 1, -1);
|
|
layout->setRowMinimumHeight(1, 20);
|
|
layout->addWidget(systemNameLabel, 2, 0);
|
|
layout->addWidget(systemName, 2, 1);
|
|
layout->addWidget(systemLocationLabel, 3, 0);
|
|
layout->addWidget(systemLocation, 3, 1);
|
|
|
|
setLayout(layout);
|
|
}
|
|
|
|
// initializePage() runs after the page has been created with the constructor
|
|
void
|
|
ConclusionPage::initializePage()
|
|
{
|
|
const auto finalPath = QDir::cleanPath(field("systemLocation").toString() + "/" + field("systemName").toString());
|
|
const auto nativePath = QDir::toNativeSeparators(finalPath);
|
|
const auto systemNameDisplay = field("systemName").toString();
|
|
|
|
systemName->setText(systemNameDisplay);
|
|
systemLocation->setText(nativePath);
|
|
}
|